/*
	File:    	AppleCarPlay_AppStub.c
	Package: 	Apple CarPlay Communication Plug-in.
	Abstract: 	n/a 
	Version: 	n/a
	
	Disclaimer: IMPORTANT: This Apple software is supplied to you, by Apple Inc. ("Apple"), in your
	capacity as a current, and in good standing, Licensee in the MFi Licensing Program. Use of this
	Apple software is governed by and subject to the terms and conditions of your MFi License,
	including, but not limited to, the restrictions specified in the provision entitled ”Public
	Software”, and is further subject to your agreement to the following additional terms, and your
	agreement that the use, installation, modification or redistribution of this Apple software
	constitutes acceptance of these additional terms. If you do not agree with these additional terms,
	please do not use, install, modify or redistribute this Apple software.
	
	Subject to all of these terms and in consideration of your agreement to abide by them, Apple grants
	you, for as long as you are a current and in good-standing MFi Licensee, a personal, non-exclusive 
	license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use, 
	reproduce, and modify the Apple Software in source form, and to use, reproduce, modify, and 
	redistribute the Apple Software, with or without modifications, in binary form. While you may not 
	redistribute the Apple Software in source form, should you redistribute the Apple Software in binary
	form, you must retain this notice and the following text and disclaimers in all such redistributions
	of the Apple Software. Neither the name, trademarks, service marks, or logos of Apple Inc. may be
	used to endorse or promote products derived from the Apple Software without specific prior written
	permission from Apple. Except as expressly stated in this notice, no other rights or licenses, 
	express or implied, are granted by Apple herein, including but not limited to any patent rights that
	may be infringed by your derivative works or by other works in which the Apple Software may be 
	incorporated.  
	
	Unless you explicitly state otherwise, if you provide any ideas, suggestions, recommendations, bug 
	fixes or enhancements to Apple in connection with this software (“Feedback”), you hereby grant to
	Apple a non-exclusive, fully paid-up, perpetual, irrevocable, worldwide license to make, use, 
	reproduce, incorporate, modify, display, perform, sell, make or have made derivative works of,
	distribute (directly or indirectly) and sublicense, such Feedback in connection with Apple products 
	and services. Providing this Feedback is voluntary, but if you do provide Feedback to Apple, you 
	acknowledge and agree that Apple may exercise the license granted above without the payment of 
	royalties or further consideration to Participant.
	
	The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR 
	IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY 
	AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR
	IN COMBINATION WITH YOUR PRODUCTS.
	
	IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES 
	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
	PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION 
	AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
	(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 
	POSSIBILITY OF SUCH DAMAGE.
	
	Copyright (C) 2007-2021 Apple Inc. All Rights Reserved. Not to be used or disclosed without permission from Apple.
*/

//===========================================================================================================================
//	This file contains sample code snippets that demonstrate how to use the plug-in APIs.  
//===========================================================================================================================

#include "AppleCarPlay_AppStub.h"
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>

#include "StringUtils.h"
#include "AirPlayReceiverServer.h"
#include "AirPlayReceiverSession.h"
#include "AirPlayVersion.h"
#include "AirPlayUtils.h"
#include "DebugServices.h"
#include "HIDKnob.h"
#include "HIDTouchScreen.h"
#include "HIDProximity.h"
#include "HIDTelephony.h"
#include "HIDMediaButtons.h"
#include "MathUtils.h"
#include "AudioUtils.h"
#include "CarPlayLanguages.h"
#include "CarPlayControlClient.h"
#include "BonjourBrowser.h"

#include "iAP2_AppStub.h"
#include "CarPlayProtocol.h"
//===========================================================================================================================
//	Internals
//===========================================================================================================================

#define kDefaultUUID			CFSTR( "3a07bd78" )
#define kMainScreenUUID			CFSTR( "e5f7a68d-7b0f-4305-984b-974f677a150b" )
#define kAltScreenUUID			CFSTR( "e5f7a68d-7b0f-4305-984b-b051a776f479" )
#define kAltScreen2UUID			CFSTR( "e5f7a68d-7b0f-4305-984b-b051a776f472" )
#define kScreenNoTimeout		( (uint64_t) INT64_C( -1 ) )

#define kHsaeVendorId						0x11c0
#define kHsaeProductId						0x0001

#define kUSBCountryCodeUnused               0
#define kUSBCountryCodeUS                   33
#define kUSBVendorTouchScreen               kHsaeVendorId
#define kUSBProductTouchScreen              kHsaeProductId
#define kUSBVendorKnobButtons               0
#define kUSBProductKnobButtons              0
#define kUSBVendorProxSensor                0
#define kUSBProductProxSensor               0

#define kUSBVendorTelephony                 kHsaeVendorId
#define kUSBProductTelephony                kHsaeProductId
#define kUSBVendorMediaButtons              kHsaeVendorId
#define kUSBProductMediaButtons             kHsaeProductId

// Prototypes

static OSStatus	_SetupUI( void );
static void		_TearDownUI( void );
static void		_RunUI( void );

static void *	_AirPlayThread( void *inArg );

static void 	_CarPlayControlClientEventCallback( CarPlayControlClientRef client, CarPlayControlClientEvent event, void *eventInfo, void *context );

static CFTypeRef
	_AirPlayHandleServerCopyProperty( 
		AirPlayReceiverServerRef	inServer, 
		CFStringRef					inProperty, 
		CFTypeRef					inQualifier, 
		OSStatus *					outErr, 
		void *						inContext );
	
static void
	_AirPlayHandleSessionCreated( 
		AirPlayReceiverServerRef	inServer, 
		AirPlayReceiverSessionRef	inSession, 
		void *						inContext );
		
static void	
	_AirPlayHandleSessionFinalized( 
		AirPlayReceiverSessionRef inSession, 
		void *inContext );

static void
	_AirPlayHandleSessionStarted( 
		AirPlayReceiverSessionRef inSession, 
		void *inContext );

static void
	_AirPlayHandleModesChanged( 
		AirPlayReceiverSessionRef 	inSession, 
		const AirPlayModeState *	inState, 
		void *						inContext );

static void	
	_AirPlayHandleRequestUI( 
		AirPlayReceiverSessionRef inSession, 
		CFStringRef inURL,
		void *inContext );
	
static CFTypeRef
	_AirPlayHandleSessionCopyProperty( 
		AirPlayReceiverSessionRef	inSession, 
		CFStringRef					inProperty, 
		CFTypeRef					inQualifier, 
		OSStatus *					outErr, 
		void *						inContext );

static OSStatus 
	_AirPlayHandleSessionControl( 	
		AirPlayReceiverSessionRef 	inSession, 
 		CFStringRef         		inCommand,
	        CFTypeRef           		inQualifier,
	        CFDictionaryRef     		inParams,
		CFDictionaryRef *   		outParams,
		void *				inContext );

static void
	_AirPlayHandleSetEnhancedSiriParams(
		AirPlayReceiverSessionRef	inSession,
		AirPlayVoiceActivationMode	inMode,
		CFStringRef			inLanguage,
		void *				inContext );

static void
	_AirPlayHandleSuggestUI(
		AirPlayReceiverSessionRef 	inSession,
		CFArrayRef			inURLs,
		void *				inContext );

static void
    _AirPlayHandleDuckAudio(
	    AirPlayReceiverSessionRef	inSession,
	    double						inDurationSecs,
	    double						inVolume,
	    void *						inContext );

static void
    _AirPlayHandleunDuckAudio(
	    AirPlayReceiverSessionRef	inSession,
	    double						inDurationSecs,
	    void *						inContext );

static OSStatus	_ParseModes( const char *inArg );
static OSStatus	_ParseModeResource( AirPlayResourceChange *inResource, const char *inArg );

static void _initialModesCommand( const char * inCommand );

static void	
	_sendGenericChangeModeRequest(
		const char *				inScreenBorrowID,
		AirPlayTransferType 		inScreenType,
		AirPlayTransferPriority		inScreenPriority,
		AirPlayConstraint			inScreenTake,
		AirPlayConstraint			inScreenBorrow,
		const char *				inAudioBorrowID,
		AirPlayTransferType 		inAudioType,
		AirPlayTransferPriority		inAudioPriority,
		AirPlayConstraint			inAudioTake,
		AirPlayConstraint			inAudioBorrow,
		AirPlayTriState				inPhone,
		AirPlaySpeechMode			inSpeech,
		AirPlayTriState				inTurnByTurn );

static void	
	_sendInitialChangeModeRequest(
		const char *				inScreenBorrowID,
		AirPlayTransferType 		inScreenType,
		AirPlayTransferPriority		inScreenPriority,
		AirPlayConstraint			inScreenTake,
		AirPlayConstraint			inScreenBorrow,
		const char *				inAudioBorrowID,
		AirPlayTransferType 		inAudioType,
		AirPlayTransferPriority		inAudioPriority,
		AirPlayConstraint			inAudioTake,
		AirPlayConstraint			inAudioBorrow,
		AirPlayTriState				inPhone,
		AirPlaySpeechMode			inSpeech,
		AirPlayTriState				inTurnByTurn,
		AirPlayEntity				inScreenPermanentEntity,
		AirPlayConstraint			inScreenPermanentTakeConstraint,
		AirPlayConstraint			inScreenPermanentBorrowConstraint,
		AirPlayEntity				inAudioPermanentEntity,
		AirPlayConstraint			inAudioPermanentTakeConstraint,
		AirPlayConstraint			inAudioPermanentBorrowConstraint );
		
static void	
	_setChangeModesStruct(
		AirPlayModeChanges *		outModeChanges,
		const char *				inScreenBorrowID,
		AirPlayTransferType 		inScreenType,
		AirPlayTransferPriority		inScreenPriority,
		AirPlayConstraint			inScreenTake,
		AirPlayConstraint			inScreenBorrow,
		const char *				inAudioBorrowID,
		AirPlayTransferType 		inAudioType,
		AirPlayTransferPriority		inAudioPriority,
		AirPlayConstraint			inAudioTake,
		AirPlayConstraint			inAudioBorrow,
		AirPlayTriState				inPhone,
		AirPlaySpeechMode			inSpeech,
		AirPlayTriState				inTurnByTurn );

static void
	_changeResourceMode(
		const char *				inBorrowID,
		AirPlayResourceID			inResourceID,
		AirPlayTransferType			inType,
		AirPlayTransferPriority		inPriority,
		AirPlayConstraint			inTakeConstraint,
		AirPlayConstraint			inBorrowOrUnborrowConstraint);

static void
	_changeAppState(
		AirPlaySpeechMode			inSpeechMode,
		AirPlayTriState				inPhoneCall,
		AirPlayTriState				inTurnByTurn);

static void _requestUI();
static void _requestUIWithURL( const char * url );
static void _requestSiriAction( AirPlaySiriAction	inAction );
static void _setNightMode( int mode );
static void _UIAppearanceUpdate( int inMode, int inSetting );
static void _setLimitedUI( int inLimitUI );
static void _AirPlayReceiverSessionEnd();
static void _CarPlayControllerConnect();
static int _CarPlayControllerContainsCString( const char * inDeviceID );
static int	_CarPlayControllerEqual();
static void _AirPlayReceiverSessionSendiAPMessage( int length, const unsigned char *ptr );

static CFArrayRef _getAudioFormats( OSStatus *outErr );
static CFArrayRef _getAudioLatencies( OSStatus *outErr );
static OSStatus _setupHIDDevices( void );
static CFArrayRef _getHIDDevices( OSStatus *outErr );
static CFArrayRef _getScreenDisplays( OSStatus *outErr );
static CFArrayRef _getScreenDisplaysWindowConfig( OSStatus *outErr );
static OSStatus _sendETCUpdate(bool inETCEnabled);

static CFDictionaryRef createInitialModesForBackupCamera( void );
static CFDictionaryRef createInitialModesForController( void );

ulog_define( CarPlayDemoApp, kLogLevelTrace, kLogFlags_Default, "CarPlayDemoApp", NULL );
#define app_ucat()					&log_category_from_name( CarPlayDemoApp )
#define app_ulog( LEVEL, ... )		ulog( app_ucat(), (LEVEL), __VA_ARGS__ )
#define app_dlog( LEVEL, ... )		dlogc( app_ucat(), (LEVEL), __VA_ARGS__ )

ulog_define( CarPlayDemoAppErr, kLogLevelTrace, kLogFlags_Default | kLogFlags_PrintError, "CarPlayDemoAppErr", NULL );
#define app_ucat_err()					&log_category_from_name( CarPlayDemoAppErr )
#define app_ulog_err( LEVEL, ... )		ulog( app_ucat_err(), (LEVEL), __VA_ARGS__ )

// Globals

static bool							gKnob			= true;
static bool							gHiFiTouch		= true;
static bool							gLoFiTouch		= false;
static bool							gTouchpad		= false;
static bool							gProxSensor		= false;
static bool							gTelephony		= true;
static bool                         gMediaButtons   = true;
static int							gPrimaryInputDevice			= kAirPlayDisplayPrimaryInputDeviceTouch;
static bool							gEnhancedRequestCarUI		= false;
static bool							gETCSupported				= false;
static AirPlayReceiverServerRef		gAirPlayServer	= NULL;
static AirPlayReceiverSessionRef	gAirPlaySession	= NULL;
static CarPlayControlClientRef		gCarPlayControlClient = NULL;
static CFMutableArrayRef			gCarPlayControllers = NULL;
static pthread_t					gAirPlayThread = 0;
static AirPlayModeChanges			gInitialModesRaw;
static AirPlayInitialPermanentEntity	gInitialPermanentEntity;
static CFDictionaryRef				gInitialModes		= NULL;
static bool							gHasInitialModes	= false;
static CFMutableArrayRef			gBluetoothIDs				= NULL;
static CFBooleanRef					gRightHandDrive				= NULL;
static CFBooleanRef					gNightMode				= NULL;
static CFBooleanRef					gLimitedUI				= NULL;
static AirPlayAppearanceMode		gAppearanceMode			= kAirPlayAppearanceMode_Light;
static AirPlayAppearanceSetting		gAppearanceSetting		= kAirPlayAppearanceSetting_Automatic;
static CFStringRef					gDeviceID				= NULL;
static CFStringRef					giOSVersionMin			= NULL;
static uint8_t						gTouchUID;
static uint8_t						gKnobUID;
static uint8_t						gProxSensorUID;
static uint8_t						gTelephonyUID;
static uint8_t                      gMediaButtonsUID;
static uint8_t						gNextDeviceUID = 0;
static int							gVideoWidth		= 1920;
static int							gVideoHeight	= 720;
static int							gWidthPhysical;
static int							gHeightPhysical;

pthread_mutex_t						authMutex = PTHREAD_MUTEX_INITIALIZER;
char								gPairingIdentity[ 128 ];
static char					        gWifiSsid [ 128 ]= { 0 };
static char					        gWifiPassword [ 128 ]= { 0 };
static char					        gOemIconPath [ 128 ]= { 0 };
static char                         gOemIconLabel [ 128 ]= { 0 };

// Current state of the virtual knob
Boolean gSelectButtonPressed = false;
Boolean gHomeButtonPressed = false;
Boolean gBackButtonPressed = false;
Boolean gSiriButtonPressed = false;
double	gXPosition = 0;
double	gYPosition = 0;
int8_t	gWheelPositionRelative = 0;
// Maximum and minimum X values reported by actual knob
#define kMaxXPosition	(1.0)
#define kMinXPosition	(-1.0)
// Maximum and minimum X values reported by actual knob
#define kMaxYPosition	(1.0)
#define kMinYPosition	(-1.0)
OSStatus	KnobUpdate( void );

static OSStatus _touchScreenUpdate( uint8_t inPress, uint16_t inX, uint16_t inY );
static OSStatus _telephonyUpdate( int inButtonIndex );
static OSStatus _mediaButtonsUpdate( int inButtonIndex );
static OSStatus _proxSensorUpdate( bool inProxSensorPresence );
	
static Boolean gSupportsEnhancedSiri = false;
static Boolean gSupportsViewAreas = false;
static Boolean gSupportsUIContext = false;
static Boolean gSupportsAltScreen = false;
static Boolean gSupportsCornerMasks = false;
static Boolean gSupportsFocusTransfer = false;
static Boolean gCarPlayStartedStatus = false;

//===========================================================================================================================
//	main
//===========================================================================================================================

//int	main( int argc, char **argv )
int     startCarplay( int argc, char **argv )
{
	app_ulog( kLogLevelWarning, "AirPlay startCarplay.\n" );
	if( gCarPlayStartedStatus ) {
		return 0;
	}
	gCarPlayStartedStatus = true;
	int					i;
	const char *		arg;
	OSStatus			err;
	char 				roleStatus[ 8 ] = { 0 };

	// To disable DAD checking, specify DUPADDRDET 0 on the INTERFACE statement.

//	system( "echo 0 > /proc/sys/net/ipv6/conf/ncm0/accept_dad" );

	// when AppleCarPlay core dump and restart, do it.

//	if( _GetUsbRoleStatus( roleStatus ) == 0 )
//	{
//		app_ulog( kLogLevelNotice, "Usb RoleStatus %s", roleStatus );
//
//		if( strnicmp( roleStatus, "gadget", 6 ) == 0 )
//		{
//			app_ulog_err( kLogLevelNotice, "Usb role is gadget, so role switch back.\n" );
//
//			system("echo usb_otg1_id=0 > /dev/hhhgpio");
//		}
//	}

	app_ulog( kLogLevelNotice, "AirPlay starting version %s\n", kAirPlaySourceVersionStr);
	signal( SIGPIPE, SIG_IGN ); // Ignore SIGPIPE signals so we get EPIPE errors from APIs instead of a signal.
	AirPlayModeChangesInit( &gInitialModesRaw );
	AirPlayInitialPermanentEntityInit( &gInitialPermanentEntity );

	// Parse command line arguments.

	for( i = 1; i < argc; )
	{
		arg = argv[ i++ ];
		if( arg == NULL ) {}
		else if( strcmp( arg, "--no-knob" ) == 0 )
		{
			gKnob = false;
			app_ulog( kLogLevelNotice, "AirPlay starting --no-knob .\n");
		}
		else if( strcmp( arg, "--no-hi-fi-touch" ) == 0 )
		{
			gHiFiTouch = false;
			app_ulog( kLogLevelNotice, "AirPlay starting --no-hi-fi-touch .\n");
		}
		else if( strcmp( arg, "--no-lo-fi-touch" ) == 0 )
		{
			gLoFiTouch = false;
			app_ulog( kLogLevelNotice, "AirPlay starting --no-lo-fi-touch .\n");
		}
		else if( strcmp( arg, "--no-touchpad" ) == 0 )
		{
			gTouchpad = false;
			app_ulog( kLogLevelNotice, "AirPlay starting --no-touchpad .\n");
		}
		else if( strcmp( arg, "--no-proxsensor" ) == 0 )
		{
			gProxSensor = false;
			app_ulog( kLogLevelNotice, "AirPlay starting --no-proxsensor .\n");
		}
		else if( strcmp( arg, "--btid" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			err = CFArrayEnsureCreatedAndAppendCString( &gBluetoothIDs, argv[ i++ ], kSizeCString );
			check_noerr( err );
			app_ulog( kLogLevelNotice, "AirPlay starting --btid .\n");
		}
		else if( strcmp( arg, "--icon-path" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			arg = argv[ i++ ];
			strcpy(gOemIconPath,arg);
			check_noerr( err );
			app_ulog( kLogLevelNotice, "AirPlay starting --icon-path = %s .\n",gOemIconPath);
		}
		else if( strcmp( arg, "--icon-label" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			arg = argv[ i++ ];
			app_ulog( kLogLevelNotice, "AirPlay starting --icon-label arg = %s .\n",arg);

			strcpy(gOemIconLabel,arg);
			check_noerr( err );
			app_ulog( kLogLevelNotice, "AirPlay starting --icon-label = %s .\n",gOemIconLabel);
		}
		else if( strcmp( arg, "--width" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			arg = argv[ i++ ];
			gVideoWidth = atoi( arg );
			check_noerr( err );
			app_ulog( kLogLevelNotice, "AirPlay starting --width = %d .\n",gVideoWidth);
		}
		else if( strcmp( arg, "--heigh" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			arg = argv[ i++ ];
			gVideoHeight = atoi( arg );
			check_noerr( err );
			app_ulog( kLogLevelNotice, "AirPlay starting --heigh = %d .\n",gVideoHeight);
		}
		else if( strcmp( arg, "--widthMM" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			arg = argv[ i++ ];
			gWidthPhysical = atoi( arg );
			check_noerr( err );
		}
		else if( strcmp( arg, "--heightMM" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			arg = argv[ i++ ];
			gHeightPhysical = atoi( arg );
			check_noerr( err );
		}
		else if( strcmp( arg, "--modes" ) == 0 )
		{
			if( i >= argc )
			{
				fprintf( stderr, "error: %s requires a value. For example:\n", arg );
				fprintf( stderr, "    --modes screen=userInitiated,anytime\n" );
				fprintf( stderr, "    --modes mainAudio=anytime,anytime\n" );
				fprintf( stderr, "    --modes phoneCall\n" );
				fprintf( stderr, "    --modes speech=speaking\n" );
				fprintf( stderr, "    --modes turnByTurn\n" );
				exit( 1 );
			}
			arg = argv[ i++ ];
			err = _ParseModes( arg );
			if( err ) exit( 1 );
			gHasInitialModes = true;
		}
		else if( strcmp( arg, "--btid" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			err = CFArrayEnsureCreatedAndAppendCString( &gBluetoothIDs, argv[ i++ ], kSizeCString );
			check_noerr( err );
		}
		else if( strcmp( arg, "--right-hand-drive" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			arg = argv[ i++ ];
			if( !strcmp ( arg, "true" ) ) 			gRightHandDrive = kCFLBooleanTrue;
			else if( !strcmp ( arg, "false" ) ) 	gRightHandDrive = kCFLBooleanFalse;
			check_noerr( err );
		}
		else if( strcmp( arg, "--enable-etc" ) == 0 )
		{
			gETCSupported = true;
		}
		else if( strcmp( arg, "--primary-input-device" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			arg = argv[ i++ ];
			gPrimaryInputDevice = atoi( arg );
		}
		else if( strcmp( arg, "--enhanced-requestcarui" ) == 0 )
		{
			gEnhancedRequestCarUI = true;
		}
		else if( strcmp( arg, "--deviceid" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			gDeviceID = CFStringCreateWithCString( kCFAllocatorDefault, argv[ i++ ], kCFStringEncodingUTF8 );
			check_noerr( err );
		}
		else if( strcmp( arg, "--iOSVersionMin" ) == 0 )
		{
			if( i >= argc ) { fprintf( stderr, "error: %s requires a value\n", arg ); exit( 1 ); }
			giOSVersionMin = CFStringCreateWithCString( kCFAllocatorDefault, argv[ i++ ], kCFStringEncodingUTF8 );
			check_noerr( err );
		}
	}
	
	if( gHasInitialModes )
	{
		gInitialModes = AirPlayCreateModesDictionary( &gInitialModesRaw, NULL, &err );
		check_noerr( err );
	}
	
	// Set up interaction with the platform UI framework.
	err = _SetupUI();
	require_noerr( err, exit );
	
	err = _setupHIDDevices();
	require_noerr( err, exit );

	// Start AirPlay in a separate thread since the demo app needs to own the main thread.
	
	err = pthread_create( &gAirPlayThread, NULL, _AirPlayThread, NULL );
	require_noerr( err, exit );
	
	// Run the main loop for the app to receive UI events. This doesn't return until the app quits.
	app_ulog_err( kLogLevelNotice, "AirPlay has been already initialized.\n" );
	_RunUI();
	app_ulog_err( kLogLevelNotice, "AirPlay has been session finished.\n" );
	
exit:
	_TearDownUI();
	return( err ? 1 : 0 );
}

int     stopCarplay( )
{
	_AirPlayReceiverSessionEnd();
	return 0;
}
//===========================================================================================================================
//	_StartAirPlayServer
//===========================================================================================================================

static OSStatus	_StartAirPlayServer()
{
	OSStatus			err = kNoErr;

	if( gAirPlayThread )
	{
		app_ulog_err( kLogLevelNotice, "AirPlay has been already initialized.\n" );
		return kAlreadyInitializedErr;
	}

	// Start AirPlay in a separate thread since the demo app needs to own the main thread.
	
	err = pthread_create( &gAirPlayThread, NULL, _AirPlayThread, NULL );
	require_noerr( err, exit );

exit:
	if( err )	app_ulog_err( kLogLevelNotice, "Start AirPlay in a separate thread err.\n" );
	return( err );
}

//===========================================================================================================================
//	_StopAirPlayServer
//===========================================================================================================================

static void	_StopAirPlayServer()
{
	if( !gAirPlayThread )
	{
		app_ulog_err( kLogLevelNotice, "AirPlay has been already stopped err.\n" );
		return;
	}
	CFRunLoopStop( CFRunLoopGetCurrent() );
	gAirPlayThread = 0;

	app_ulog_err( kLogLevelNotice, "AirPlay is stopped successfully.\n" );
}

//===========================================================================================================================
//	_SetupUI
//===========================================================================================================================

static OSStatus	_SetupUI( void )
{
	OSStatus			err = kNoErr;

	// $$$ TODO: Set up the UI framework application and window for drawing and receiving user events.
	//InitStreamPlayer();

	require_noerr( err, exit );

exit:
	if( err ) _TearDownUI();
	return( err );
}

//===========================================================================================================================
//	_TearDownUI
//===========================================================================================================================

static void	_TearDownUI( void )
{
	app_ulog_err( kLogLevelNotice, "AirPlay _TearDownUI.\n" );
	// $$$ TODO: Destroy up the UI framework application and window for drawing and receiving user events.
}

//===========================================================================================================================
//	_RunUI
//===========================================================================================================================

static void	_RunUI( void )
{
	app_ulog_err( kLogLevelWarning, "AirPlay _RunUI.\n" );
	// $$$ TODO: Get user input and send HID reports to AirPlay. 
	// For an example of creating a virtual HID knob report see KnobUpdate (void).
#if 0 
	CarPlayInterfaceDelegate    delegate;

	CarPlayInterfaceDelegateInit(&delegate);
	delegate.initialModesCommand_f	= _initialModesCommand;
	delegate.changeMode_f 			= _sendGenericChangeModeRequest;
	delegate.initialChangeModes_f	= _sendInitialChangeModeRequest;
	delegate.changeResourceMode_f	= _changeResourceMode;
	delegate.changeAppState_f		= _changeAppState;
	delegate.requestUi_f			= _requestUI;
	delegate.requestUiWithUrl_f		= _requestUIWithURL;
	delegate.requestSiriAction_f 	= _requestSiriAction;
	delegate.setNightMode_f			= _setNightMode;
	delegate.uiAppearanceUpdate_f	= _UIAppearanceUpdate;
	delegate.setLimitedUI_f			= _setLimitedUI;
	delegate.touchScreenUpdate_f	= _touchScreenUpdate;
	delegate.telephonyUpdate_f		= _telephonyUpdate;
	delegate.mediaButtonUpdate_f	= _mediaButtonsUpdate;
	delegate.end_f					= _AirPlayReceiverSessionEnd;

	CarPlayInterfaceSetDelegate(&delegate);
#endif
	iAP2InterfaceInitial();

	CarPlay2iAP2InterfaceDelegate	c2iDelegate;

	CarPlay2iAP2InterfaceDelegateInit( &c2iDelegate );
	c2iDelegate.carPlayStartBonjour_f	= _CarPlayControllerConnect;
	c2iDelegate.sendiAPMessage_f 		= _AirPlayReceiverSessionSendiAPMessage;
	c2iDelegate.startAirPlayServer_f	= _StartAirPlayServer;
	c2iDelegate.end_f					= _AirPlayReceiverSessionEnd;
	c2iDelegate.stopAirPlayServer_f		= _StopAirPlayServer;
	c2iDelegate.controllerEqual_f				= _CarPlayControllerEqual;
	CarPlay2iAP2InterfaceSetDelegate( &c2iDelegate );

	app_ulog( kLogLevelNotice, "CarPlayHeadUnit SSID is  %s.\n", gWifiSsid);
	app_ulog( kLogLevelNotice, "CarPlayHeadUnit PASSWORD is  %s.\n", gWifiPassword);
//	sendIAP2WiFiInformation("AndroidAP_8853","12345678",1,"1");
	sendIAP2WiFiInformation(gWifiSsid, gWifiPassword, 1, "1");
	iAP2InterfaceBluetoothSetup();
	app_ulog( kLogLevelNotice, "CFMutableArrayRef CarPlayControllers is empty.\n" );

}

//===========================================================================================================================
//	_CarPlayControllerEqual
//===========================================================================================================================

static int _CarPlayControllerEqual(void)
{
    CFIndex count = CFArrayGetCount(gCarPlayControllers);
    if (count == 0)
    {
        app_ulog(kLogLevelNotice, "CFMutableArrayRef CarPlayControllers is empty.\n");
        return 0;
    }

    for (CFIndex i = 0; i < count; i++)
    {
        CarPlayControllerRef controller = (CarPlayControllerRef)CFArrayGetValueAtIndex(gCarPlayControllers, i);
        uint8_t deviceID[6];
        uint8_t deviceIDCString[18];
        CarPlayControllerGetBluetoothMacAddress(controller, deviceID);
        app_ulog(kLogLevelNotice, "CFMutableArrayRef CarPlayControllers deviceID %02x:"
        		"%02x:%02x:%02x:%02x:%02x \n" ,deviceID[0],deviceID[1],deviceID[2],deviceID[3],
        		deviceID[4],deviceID[5]);

        MACAddressToCString(deviceID, deviceIDCString);
        if (stricmp(gDeviceId, (char *)deviceIDCString) == 0)
        {
            return 1;
        }
    }
    return 0;
}


//===========================================================================================================================
//	_CarPlayControllerConnect
//===========================================================================================================================

static void	_CarPlayControllerConnect()
{
	CarPlayControllerRef		controller;
	OSStatus 					err;
	CFIndex						count;
	uint8_t						deviceID[ 6 ];
	uint8_t						deviceIDCString[ 18 ];

	count = CFArrayGetCount( gCarPlayControllers );

	if( count == 0 )
	{
		app_ulog_err( kLogLevelNotice, "CarPlayControllerConnect strerr local CarPlayControllers count is 0.\n" );
		return;
	}

	app_ulog_err( kLogLevelNotice, "CarPlayControllerConnect strerr local CarPlayControllers count is %d .\n",count );

	int targetIndex = 0;
    for (CFIndex i = 0; i < count; i++)
    {
        CarPlayControllerRef controller = (CarPlayControllerRef)CFArrayGetValueAtIndex(gCarPlayControllers, i);
        CarPlayControllerGetBluetoothMacAddress(controller, deviceID);
        app_ulog(kLogLevelNotice, "_CarPlayControllerConnect CarPlayControllers deviceID %02x:"
        		"%02x:%02x:%02x:%02x:%02x \n" ,deviceID[0],deviceID[1],deviceID[2],deviceID[3],
        		deviceID[4],deviceID[5]);

        MACAddressToCString(deviceID, deviceIDCString);
        if( stricmp( gDeviceId, deviceIDCString ) == 0 )
        {
        	targetIndex = i;
            break;
        }
    }

	controller = (CarPlayControllerRef) CFArrayGetValueAtIndex( gCarPlayControllers, targetIndex );

	CarPlayControllerGetBluetoothMacAddress( controller, deviceID );
	MACAddressToCString( deviceID, deviceIDCString );

	if( stricmp( gDeviceId, deviceIDCString ) != 0 )
	{
		app_ulog_err( kLogLevelNotice, "CarPlayControllerConnect strerr iAP2 deviceId: %s not equal Add/Update deviceID: %s.\n", gDeviceId, deviceIDCString );
		return;
	}

	for( int i = 0; i < 20; i++ ) {
		err = CarPlayControlClientConnect( gCarPlayControlClient, controller );
		app_ulog( kLogLevelNotice, "CarPlayControlClientConnect %s: %#m\n", err ? "failed" : "succeeded", err );

		if( err != kNoErr ) {
			sleep( 1 );
			continue;
		} else {
			break;
		}
	}
}

//===========================================================================================================================
//	_CarPlayControlClientEventCallback
//===========================================================================================================================
static void _CarPlayControlClientEventCallback( CarPlayControlClientRef client, CarPlayControlClientEvent event, void *eventInfo, void *context )
{
    (void) client;
	(void) context;
	CarPlayControllerRef controller = (CarPlayControllerRef)eventInfo;
	OSStatus err;

	if (event == kCarPlayControlClientEvent_AddOrUpdateController)
	{
		CFIndex 	count, serviceCount;
		uint8_t		deviceID[ 6 ];
		uint8_t		deviceIDCString[ 18 ];

		// analyse the CarPlayController

		// deviceID

		CarPlayControllerGetBluetoothMacAddress( controller, deviceID );
		MACAddressToCString( deviceID, deviceIDCString );

		// servicesCount

		CarPlayControllerGetServicesCount( controller, &serviceCount );

		if( serviceCount == 2 )
		{
			app_ulog( kLogLevelNotice, "ignore serviceCount 2 of bonjour.\n" );
			return;
		}

		count = CFArrayGetCount( gCarPlayControllers );

		app_ulog( kLogLevelNotice, "CarPlayControlClientEvent Add/Update CarPlayController deviceID: %s, CarPlayControllers Count: %d.\n", deviceIDCString, count );

		// No iAP2 deviceID, iAP2 Authentication may not be complete yet.

		if( strlen( gDeviceId ) == 0 )
		{
			app_ulog_err( kLogLevelNotice, "No iAP2 deviceID, iAP2 Authentication may not be complete yet.\n" );

//			if( count > 0 )	CFArrayRemoveAllValues( gCarPlayControllers ); //Tinker
			CFArrayAppendValue( gCarPlayControllers, controller );

			goto exit;
		}

		// With iAP2 deviceID, iAP2 Authentication already is complete. If iAP2 deviceID not equal deviceID, ignore.

		if( stricmp( gDeviceId, deviceIDCString ) != 0 )
		{
			app_ulog_err( kLogLevelNotice, "iAP2 deviceId: %s not equal Add/Update deviceID: %s, ignore the CarPlayController.\n", gDeviceId, deviceIDCString );
			goto exit;
		}

		// iAP2 deviceID equal deviceID, then update bonjour services.

		if( count > 0 )	CFArrayRemoveAllValues( gCarPlayControllers );
		CFArrayAppendValue( gCarPlayControllers, controller );

		app_ulog_err( kLogLevelNotice, "iAP2 deviceID equal deviceID, then update bonjour services.\n" );

		BonjourCarPlayAvailability();

	}
	else if (event == kCarPlayControlClientEvent_RemoveController)
	{
		CFIndex 	ndx, count;
		uint8_t		deviceID[ 6 ];
		uint8_t		deviceIDCString[ 18 ];

		count = CFArrayGetCount( gCarPlayControllers );
		ndx = CFArrayGetFirstIndexOfValue( gCarPlayControllers, CFRangeMake(0, count), controller );

		app_ulog_err( kLogLevelNotice, "CarPlayControlClientEvent Remove event received count: %d, ndx: %d\n", count, ndx );

		if( ndx == 0 )	CFArrayRemoveAllValues( gCarPlayControllers );
	}
	else
	{
		app_ulog_err( kLogLevelNotice, "CarPlayControlClientEvent event type %d received\n", (int)event );
	}

exit:
	return;
}

//===========================================================================================================================
//	_AirPlayThread
//===========================================================================================================================

static void *	_AirPlayThread( void *inArg )
{
	OSStatus							err;
	AirPlayReceiverServerDelegate		delegate;
	
	(void) inArg;

	// Create the AirPlay server. This advertise via Bonjour and starts listening for connections.
	
	err = AirPlayReceiverServerCreate( &gAirPlayServer );
	require_noerr( err, exit );
	
	// Register ourself as a delegate to receive server-level events, such as when a session is created.
	
	AirPlayReceiverServerDelegateInit( &delegate );
	delegate.copyProperty_f		= _AirPlayHandleServerCopyProperty;
	delegate.sessionCreated_f = _AirPlayHandleSessionCreated;
	AirPlayReceiverServerSetDelegate( gAirPlayServer, &delegate );

	// CarPlay Control

	err = CarPlayControlClientCreateWithServer( &gCarPlayControlClient, gAirPlayServer, _CarPlayControlClientEventCallback, NULL );
	require_noerr( err, exit );

	gCarPlayControllers = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
	require_action( gCarPlayControllers, exit, err = kNoMemoryErr );
	
	// Start the server and run until the app quits.
	
	AirPlayReceiverServerStart( gAirPlayServer );
	CarPlayControlClientStart( gCarPlayControlClient );
	CFRunLoopRun();
	CarPlayControlClientStop( gCarPlayControlClient );
	AirPlayReceiverServerStop( gAirPlayServer );

exit:
	CFReleaseNullSafe( gCarPlayControllers );
	CFReleaseNullSafe( gCarPlayControlClient );
	CFReleaseNullSafe( gAirPlayServer );
	return( NULL );
}

//===========================================================================================================================
//	_AirPlayHandleServerCopyProperty
//===========================================================================================================================

static CFTypeRef
	_AirPlayHandleServerCopyProperty( 
		AirPlayReceiverServerRef	inServer, 
		CFStringRef					inProperty, 
		CFTypeRef					inQualifier, 
		OSStatus *					outErr, 
		void *						inContext )
{
	CFTypeRef		value = NULL;
	OSStatus		err;
	
	(void) inServer;
	(void) inQualifier;
	(void) inContext;
	
	if( 0 ) {}

	// AudioFormats
	
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_AudioFormats ) ) )
	{
		value = _getAudioFormats( &err );
		require_noerr( err, exit );
		CFRetain( value );
	}
	
	// AudioLatencies
	
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_AudioLatencies ) ) )
	{
		value = _getAudioLatencies( &err );
		require_noerr( err, exit );
		CFRetain( value );
	}
	
	// BluetoothIDs
	
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_BluetoothIDs ) ) )
	{
		if( gBluetoothIDs == NULL )
		{
			char	bluetoothAddress[ 20 ] = { 0 };
			_getBluetoothAddress( bluetoothAddress );

			err = CFArrayEnsureCreatedAndAppendCString( &gBluetoothIDs, bluetoothAddress, strlen( bluetoothAddress ) );
			require_noerr( err, exit );
		}

		value = gBluetoothIDs;
		require_action_quiet( value, exit, err = kNotHandledErr );
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_RightHandDrive ) ) )
    {
        value = gRightHandDrive;
        require_action_quiet( value, exit, err = kNotHandledErr );
        CFRetain( value );
    }
    else if( CFEqual( inProperty, CFSTR( kAirPlayKey_NightMode ) ) )
    {
        value = gNightMode;
        require_action_quiet( value, exit, err = kNotHandledErr );
        CFRetain( value );
    }
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_LimitedUI ) ) )
	{
		if( !gLimitedUI )	gLimitedUI = kCFLBooleanFalse;
		value = gLimitedUI;
		require_action_quiet( value, exit, err = kNotHandledErr );
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_LimitedUIElements ) ) )
	{
		CFMutableArrayRef features;

		features = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
		require_action_quiet( features, exit, err = kNoMemoryErr );

		CFArrayAppendValue( features, CFSTR( kAirPlayLimitedUIElement_SoftKeyboard ) );
		CFArrayAppendValue( features, CFSTR( kAirPlayLimitedUIElement_SoftPhoneKeypad ) );
		CFArrayAppendValue( features, CFSTR( kAirPlayLimitedUIElement_NonMusicLists ) );
		CFArrayAppendValue( features, CFSTR( kAirPlayLimitedUIElement_MusicLists ) );

		value = features;
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_VehicleInformation ) ) )
	{
		CFMutableDictionaryRef vehicleDict;

		vehicleDict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
		require_action_quiet( vehicleDict, exit, err = kNotHandledErr );
		if( gETCSupported ) {
			// Support ETC
			CFMutableDictionaryRef dict = NULL;
			dict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			if( dict )
			{
				// Start with ETC active
				CFDictionarySetBoolean( dict, CFSTR( kAirPlayKey_Active), true );

				CFDictionarySetValue( vehicleDict, CFSTR( kAirPlayVehicleInformation_ETC), dict );
				ForgetCF( &dict );
				dict = NULL;
			}
		}
		if( CFDictionaryGetCount( vehicleDict ) == 0 ) {
			ForgetCF( &vehicleDict );
			err = kNotHandledErr;
			goto exit;
		} else {
			value = vehicleDict;
        	CFRetain( value );
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_ExtendedFeatures ) ) )
	{
		CFMutableArrayRef extendedFeatures;

		extendedFeatures = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
		require_action_quiet( extendedFeatures, exit, err = kNotHandledErr );

		CFArrayAppendValue( extendedFeatures, CFSTR( kAirPlayExtendedFeature_VocoderInfo ) );

		if( gEnhancedRequestCarUI ) {
			CFArrayAppendValue( extendedFeatures, CFSTR( kAirPlayExtendedFeature_EnhancedRequestCarUI ) );
		}

		if( CFArrayGetCount( extendedFeatures ) == 0 ) {
			ForgetCF( &extendedFeatures );
		} else {
			value = extendedFeatures;
        	CFRetain( value );
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_OEMIcons ) ) )
	{
		CFTypeRef obj;
		CFMutableDictionaryRef iconDict;
		CFMutableArrayRef iconsArray;
		
		iconsArray = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
		require_action_quiet( iconsArray, exit, err = kNotHandledErr );
		
		// Add icons for each required size
//		obj = CFDataCreateWithFilePath( "/data/shark/carplay_home.png", NULL );
		obj = CFDataCreateWithFilePath( gOemIconPath, NULL );
		if( obj ) {
			iconDict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			if( iconDict ) {
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_WidthPixels ), 120 );
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_HeightPixels ), 120 );
				CFDictionarySetBoolean( iconDict, CFSTR( kAirPlayOEMIconKey_Prerendered ), true );
				CFDictionarySetValue( iconDict, CFSTR( kAirPlayOEMIconKey_ImageData ), obj );
				CFArrayAppendValue( iconsArray, iconDict );
				CFRelease( iconDict );
				iconDict = NULL;
			}
			CFRelease( obj );
		}
//		obj = CFDataCreateWithFilePath( "/data/shark/carplay_home.png", NULL );
		obj = CFDataCreateWithFilePath( gOemIconPath, NULL );
		if( obj ) {
			iconDict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			if( iconDict ) {
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_WidthPixels ), 180 );
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_HeightPixels ), 180 );
				CFDictionarySetBoolean( iconDict, CFSTR( kAirPlayOEMIconKey_Prerendered ), true );
				CFDictionarySetValue( iconDict, CFSTR( kAirPlayOEMIconKey_ImageData ), obj );
				CFArrayAppendValue( iconsArray, iconDict );
				CFRelease( iconDict );
				iconDict = NULL;
			}
			CFRelease( obj );
		}
//		obj = CFDataCreateWithFilePath( "/data/shark/carplay_home.png", NULL );
		obj = CFDataCreateWithFilePath( gOemIconPath, NULL );
		if( obj ) {
			iconDict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			if( iconDict ) {
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_WidthPixels ), 256 );
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_HeightPixels ), 256 );
				CFDictionarySetBoolean( iconDict, CFSTR( kAirPlayOEMIconKey_Prerendered ), true );
				CFDictionarySetValue( iconDict, CFSTR( kAirPlayOEMIconKey_ImageData ), obj );
				CFArrayAppendValue( iconsArray, iconDict );
				CFRelease( iconDict );
				iconDict = NULL;
			}
			CFRelease( obj );
		}
		if( CFArrayGetCount( iconsArray ) > 0) {
			value = iconsArray;
			CFRetain( value );
		} else {
			CFRelease( iconsArray );
			err = kNotHandledErr;
			goto exit;
		}
	}
    else if (CFEqual(inProperty, CFSTR(kAirPlayProperty_OEMIconLabel))) {
        if (strlen( gOemIconLabel ) != 0) {
            value = CFStringCreateWithCString( kCFAllocatorDefault, gOemIconLabel, kCFStringEncodingUTF8 );
            CFRetain(value);
        }
        else {
            err = kNotHandledErr;
            goto exit;
        }
    }
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_Displays ) ) )
	{
		value = _getScreenDisplays( &err );
		// value = _getScreenDisplaysWindowConfig( &err );
		require_noerr( err, exit );
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_HIDDevices ) ) )
	{
		value = _getHIDDevices( &err );
		require_noerr( err, exit );
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_ClientOSBuildVersionMin ) ) )
	{
		if( giOSVersionMin )
		{
			value = giOSVersionMin; // ie. CFSTR( "11D257" )
			CFRetain( value );
		}
		else
		{
			err = kNotHandledErr;
			goto exit;
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_DeviceID ) ) )
	{
		// [ 89911 ] The accessory's Wi-Fi MAC address can be found in ATS > CarPlay session > Info Response > deviceID

//		uint8_t		outMACAddress[ 6 ];
//		char		MACAddressCString[ 20 ] = { 0 };
//
//		GetInterfaceMACAddress( "wlan0", outMACAddress );
//		MACAddressToCString( outMACAddress, MACAddressCString ); //从外面传进来

//		gDeviceID = CFStringCreateWithCString( kCFAllocatorDefault, MACAddressCString, kCFStringEncodingUTF8 );

		value = gDeviceID;
		require_action_quiet( value, exit, err = kNotHandledErr );
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_AccessoryEnabledFeatures ) ) )
	{
		CFMutableArrayRef features;

		features = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
		require_action_quiet( features, exit, err = kNoMemoryErr );

		// Enable if controller (and accessory supports each feature)
		if( gSupportsAltScreen )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_AltScreen ) );
		if( gSupportsUIContext )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_UIContext ) );
		if( gSupportsEnhancedSiri )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_EnhancedSiri ) );
		if( gSupportsViewAreas )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_ViewAreas ) );
		/*
		if( gSupportsCornerMasks )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_CornerMasks ) );
		*/

		/*
		if( gSupportsFocusTransfer )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_FocusTransfer ) );
		*/

		value = features;
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_InfoAltScreenSuggestUIURLs ) ) )
	{
		if( gSupportsAltScreen )
		{
			// Include any URLs that you want the device to suggest using suggestUI
			CFMutableArrayRef urls;

			urls = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
			require_action_quiet( urls, exit, err = kNotHandledErr );

			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ScreenURLsInstrumentClusterMap ) );
			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ScreenURLsInstrumentClusterInstructionCard ) );

			value = urls;
			CFRetain( value );
		}
		else
		{
			err = kNotHandledErr;
			goto exit;
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_InfoUIContextLastOnDisplayURLs ) ) )
	{
		if( gSupportsUIContext )
		{
			// Include any URLs that you want to support
			CFMutableArrayRef urls;

			urls = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
			require_action_quiet( urls, exit, err = kNotHandledErr );

			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextMaps ) );
			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextPhone ) );
			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextMessages ) );
			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextNowPlaying ) );

			value = urls;
			CFRetain( value );
		}
		else
		{
			err = kNotHandledErr;
			goto exit;
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_InfoUIContextNowOnDisplayURLs ) ) )
	{
		if( gSupportsUIContext )
		{
			// Include any URLs that you want to support
			CFMutableArrayRef urls;

			urls = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
			require_action_quiet( urls, exit, err = kNotHandledErr );

			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextNowPlaying ) );
			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextMaps ) );

			value = urls;
			CFRetain( value );
		}
		else
		{
			err = kNotHandledErr;
			goto exit;
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_EnhancedSiriInfo ) ) )
	{
		if( gSupportsEnhancedSiri )
		{
			CFMutableDictionaryRef dict;
			CFMutableArrayRef languages;

			dict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			require_action_quiet( dict, exit, err = kNoMemoryErr );

			languages = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
			if( !languages )
			{
				CFRelease( dict );
				err = kNoMemoryErr;
				goto exit;
			}
			// CFDictionarySetBoolean( dict, CFSTR( kAirPlayKey_EnhancedSiriButton ), true );
			// CFDictionarySetBoolean( dict, CFSTR( kAirPlayKey_EnhancedSiriVoice ), false );
			CFDictionarySetInt64( dict, CFSTR( kAirPlayKey_SupportedSiriTriggerZones ), kAirPlaySiriTriggerZone_NotApplicable );
			CFDictionarySetValue( dict, CFSTR( kAirPlayKey_VoiceModelCurrentLanguage ), CFSTR( kLanguageRegion_Chinese_MandarinChina ) );

			CFArrayAppendValue( languages, CFSTR( kLanguageRegion_English_UnitedStates ) );
			CFArrayAppendValue( languages, CFSTR( kLanguageRegion_Chinese_MandarinChina ) );
			CFDictionarySetValue( dict, CFSTR( kAirPlayKey_VoiceModelSupportedLanguages ), languages );
			CFRelease( languages );
			value = dict;
			CFRetain( dict );
		}
		else
		{
			err = kNotHandledErr;
			goto exit;
		}
	}
	else
	{
		err = kNotHandledErr;
		goto exit;
	}
	err = kNoErr;
	
exit:
	if( outErr ) *outErr = err;
	return( value );
}

//===========================================================================================================================
//	_AirPlayReceiverSessionEnd
//===========================================================================================================================

static void _AirPlayReceiverSessionEnd()
{
	app_ulog( kLogLevelNotice, "ends the current AirPlay session.\n" );
	gCarPlayStartedStatus = false;

	if( gAirPlaySession )	AirPlayReceiverSessionEnd( gAirPlaySession );
}

//===========================================================================================================================
//	_AirPlayHandleSessionCreated
//===========================================================================================================================

static void
	_AirPlayHandleSessionCreated( 
		AirPlayReceiverServerRef	inServer, 
		AirPlayReceiverSessionRef	inSession, 
		void *						inContext )
{
	AirPlayReceiverSessionDelegate		delegate;
	(void) inServer;
	(void) inContext;
	
	app_ulog_err( kLogLevelNotice, "AirPlay session created.\n" );
	gAirPlaySession = inSession;
	
	// Register ourself as a delegate to receive session-level events, such as modes changes.
	
	AirPlayReceiverSessionDelegateInit( &delegate );
	delegate.finalize_f		= _AirPlayHandleSessionFinalized;
	delegate.started_f		= _AirPlayHandleSessionStarted;
	delegate.copyProperty_f	= _AirPlayHandleSessionCopyProperty;
	delegate.modesChanged_f	= _AirPlayHandleModesChanged;
	delegate.requestUI_f	= _AirPlayHandleRequestUI;
	delegate.control_f		= _AirPlayHandleSessionControl;
	delegate.setEnhancedSiriParams_f = _AirPlayHandleSetEnhancedSiriParams;
	delegate.suggestUI_f = _AirPlayHandleSuggestUI;
	delegate.duckAudio_f	= _AirPlayHandleDuckAudio;
	delegate.unduckAudio_f  = _AirPlayHandleunDuckAudio;
	AirPlayReceiverSessionSetDelegate( inSession, &delegate );

	AirPlaySessionCreated();

	// gSupportsViewAreas = AirPlayReceiverSessionHasFeatureViewAreas( inSession );
	// gSupportsUIContext = AirPlayReceiverSessionHasFeatureUIContext( inSession );
	// gSupportsEnhancedSiri =  AirPlayReceiverSessionHasFeatureEnhancedSiri( inSession );
	// gSupportsAltScreen = AirPlayReceiverSessionHasFeatureAltScreen( inSession );
	// gSupportsCornerMasks = AirPlayReceiverSessionHasFeatureCornerMasks( inSession );
	// gSupportsFocusTransfer = AirPlayReceiverSessionHasFeatureFocusTransfer( inSession );
}

//===========================================================================================================================
//	_AirPlayHandleSessionFinalized
//===========================================================================================================================

static void	_AirPlayHandleSessionFinalized( AirPlayReceiverSessionRef inSession, void *inContext )
{
	(void) inSession;
	(void) inContext;

	// Do not delete gCarPlayControllers if support wireless CarPlay.
	gCarPlayStartedStatus = false;
	gAirPlaySession = NULL;
	app_ulog_err( kLogLevelNotice, "AirPlay session ended.\n" );

	carplaySessionStop();
	AirPlaySessionFinalized();
	_StopAirPlayServer();
}

//===========================================================================================================================
//	_GetMACAddressByIP
//===========================================================================================================================

static void _GetMACAddressByIP( const char * inCmd, const char * inIPAddr, char * outMACAddr )
{
    FILE *  file;
    char    buffer[ 128 ];
    char *  offset;

    file = popen( inCmd, "r" );
    if( !file )
    {
        app_ulog_err( kLogLevelNotice, "popen %s failed.\n", inCmd );
        goto exit;
    }

    while( fgets( buffer, 128, file ) )
    {
        offset = strstr( buffer, inIPAddr );
        if( offset )
        {
            offset = strstr( offset, "lladdr" );
            strcpy( outMACAddr, offset + 7 );
            outMACAddr[ 17 ] = '\0';
        }
    }

exit:
    if( file )
    {
        pclose( file );
        file = NULL;
    }
}

//===========================================================================================================================
//	_AirPlayHandleSessionStarted
//===========================================================================================================================

static void _AirPlayHandleSessionStarted( AirPlayReceiverSessionRef inSession, void *inContext )
{
	OSStatus error;
	CFNumberRef value;
	char	peerAddrCString[ 256 ] = { 0 };
	char	remoteMACAddress[ 18 ];

	(void) inContext;
	app_ulog( kLogLevelNotice, "AirPlay session started.\n" );

	carplaySessionStart();

	// The network interface name that is hosting the session
	CFStringRef ifName = (CFStringRef)AirPlayReceiverSessionCopyProperty(inSession, 0, CFSTR(kAirPlayProperty_InterfaceName), NULL, &error );

	// The remote IP address of the CarPlay session
	CFStringRef peerAddr = (CFStringRef)AirPlayReceiverSessionCopyProperty(inSession, 0, CFSTR(kAirPlayProperty_PeerIPAddress), NULL, &error );

	// Start a new iAP2 session over the CarPlay control channel only if the current CarPlay session is over wireless.
	// Disconnecting iAP2 over Bluetooth should only occur when disableBluetooth is received in the AirPlayReceiverSessionControl_f
	// delegate.
	CFNumberRef transportTypeNumber = (CFNumberRef)AirPlayReceiverSessionCopyProperty( inSession, 0, CFSTR(kAirPlayProperty_TransportType), NULL, &error );

	if( error == kNoErr && transportTypeNumber ) {
		uint32_t transportType;

		CFNumberGetValue( (CFNumberRef) transportTypeNumber, kCFNumberSInt32Type, &transportType );
		if( NetTransportTypeIsWiFi( transportType ) ) {
			app_ulog( kLogLevelNotice, "AirPlay session started,is WIFI.\n" );
//			CFStringGetCString( peerAddr, peerAddrCString, 256, kCFStringEncodingUTF8 );
//			_GetMACAddressByIP( "ip neigh", peerAddrCString, remoteMACAddress ); //这里有个crash
//
//
//			app_ulog( kLogLevelNotice, "The remote device of the CarPlay session IPAddress: %s, remoteMACAddress: %s.\n", peerAddrCString, remoteMACAddress );
//
//			// This is a CarPlay session over wireless, start iAP2 over CarPlay.  For sending messages use
//			// AirPlayReceiverSessionSendiAPMessage() and for receiving message use the kAirPlayCommand_iAPSendMessage command
//			// in the AirPlayReceiverSessionControl_f delegate.
//			if( giAP2TransportType == kiAP2TransportType_HostUsb )
//			{
//				app_ulog_err( kLogLevelNotice, "Dont' Start a new iAP2 session over the CarPlay control channel because transportType: %d.\n", giAP2TransportType );
//				gWireConflictWireless = 1;
//				return;
//			}
			app_ulog( kLogLevelNotice, "Start a new iAP2 session over the CarPlay control channel.\n" );
			iAP2OverCarPlaySetup();
		}
	}
}

//===========================================================================================================================
//	_AirPlayHandleSessionCopyProperty
//===========================================================================================================================

static CFTypeRef
	_AirPlayHandleSessionCopyProperty( 
		AirPlayReceiverSessionRef	inSession, 
		CFStringRef					inProperty, 
		CFTypeRef					inQualifier, 
		OSStatus *					outErr, 
		void *						inContext )
{
	CFTypeRef		value = NULL;
	OSStatus		err;
	
	(void) inSession;
	(void) inQualifier;
	(void) inContext;
	
	// Modes
	
	if( CFEqual( inProperty, CFSTR( kAirPlayProperty_Modes ) ) )
	{
		//value = createInitialModesForBackupCamera();
		//value = createInitialModesForController();
		value = gInitialModes;
		require_action_quiet( value, exit, err = kNotHandledErr );
		CFRetain( value );
	}
	else
	{
		err = kNotHandledErr;
		goto exit;
	}
	err = kNoErr;
	
exit:
	if( outErr ) *outErr = err;
	return( value );
}

//===========================================================================================================================
//	_AirPlayHandleModesChanged
//===========================================================================================================================

static void
	_AirPlayHandleModesChanged( 
		AirPlayReceiverSessionRef 	inSession, 
		const AirPlayModeState *	inState, 
		void *						inContext )
{
	(void) inSession;
	(void) inContext;

	static AirPlayModeState oldState;

	if( oldState.screen 		== inState->screen &&
		oldState.permScreen 	== inState->permScreen &&
		oldState.mainAudio		== inState->mainAudio &&
		oldState.permMainAudio 	== inState->permMainAudio &&
		oldState.speech.entity 	== inState->speech.entity &&
		oldState.speech.mode 	== inState->speech.mode &&
		oldState.phoneCall 		== inState->phoneCall &&
		oldState.turnByTurn 	== inState->turnByTurn )
	{
		return;
	}

	memcpy( &oldState, inState, sizeof( AirPlayModeState ) );

	app_ulog( kLogLevelNotice, "Modes changed: screen %s (perm %s), mainAudio %s (perm %s), speech %s (%s), phone %s, turns %s\n", 
		AirPlayEntityToString( inState->screen ), AirPlayEntityToString( inState->permScreen ),
		AirPlayEntityToString( inState->mainAudio ), AirPlayEntityToString( inState->permMainAudio),
		AirPlayEntityToString( inState->speech.entity ), AirPlaySpeechModeToString( inState->speech.mode ),
		AirPlayEntityToString( inState->phoneCall ), AirPlayEntityToString( inState->turnByTurn ) );

	DongleCarPlayModeState dongleModes;
	dongleModes.screen = inState->screen;
	dongleModes.mainAudio = inState->mainAudio;
	dongleModes.phoneCall = inState->phoneCall;
	dongleModes.turnByTurn = inState->turnByTurn;
	dongleModes.speech.entity = inState->speech.entity;
	dongleModes.speech.mode = inState->speech.mode;
	modesChangeCB(&dongleModes);
//      	        handleSessionModesChanged( inState->screen, inState->permScreen, inState->mainAudio, inState->permMainAudio,
//		inState->speech.entity, inState->speech.mode, inState->phoneCall, inState->turnByTurn );
}

//===========================================================================================================================
//	_AirPlayHandleRequestUI
//===========================================================================================================================

static void	_AirPlayHandleRequestUI( AirPlayReceiverSessionRef inSession, CFStringRef inURL, void *inContext )
{
	const char *urlStr = NULL;
	
	(void) inSession;
	(void) inContext;

	if( inURL ) {
		urlStr = CFStringGetCStringPtr( inURL, kCFStringEncodingUTF8 );
	}
	
	app_ulog( kLogLevelNotice, "Request accessory UI: \"%s\"\n", urlStr ? urlStr : "NULL" );

//	handleSessionRequestUi();
	returnNativeUICB();
}

static void _AirPlayReceiverSessionSendiAPMessage( int length, const unsigned char *ptr )
{
	CFDataRef		data;

	data = CFDataCreate( NULL, ptr, length );

	if( gAirPlaySession )	AirPlayReceiverSessionSendiAPMessage( gAirPlaySession, data, NULL, NULL );
}

//===========================================================================================================================
//	_AirPlayHandleSessionControl
//===========================================================================================================================

static OSStatus 
	_AirPlayHandleSessionControl( 	
		AirPlayReceiverSessionRef 	inSession, 
 		CFStringRef         		inCommand,
        CFTypeRef           		inQualifier,
        CFDictionaryRef     		inParams,
        CFDictionaryRef *   		outParams,
		void *						inContext )
{
	OSStatus err;

	(void) inSession;
    (void) inQualifier;
    (void) inParams;
    (void) outParams;
	(void) inContext;

	if( CFEqual( inCommand, CFSTR( kAirPlayCommand_DisableBluetooth ) ) )
    {
		char address[64] = { 0 };

		CFDictionaryGetCString( inParams, CFSTR( kAirPlayKey_DeviceID ), address, 64, NULL );
		app_ulog( kLogLevelNotice, "Disable Bluetooth session control request MAC address: %s\n", address );

//		handleSessionControlDisableBluetooth( address );
		disableBluetoothCB();

		AirPlaySessionControlDisableBluetooth();
    	err = kNoErr;
    }
	else if( CFEqual( inCommand, CFSTR( kAirPlayCommand_iAPSendMessage ) ) )
	{
		CFDataRef data = (CFDataRef)CFDictionaryGetValue( inParams, CFSTR( kAirPlayKey_Data ) );

		_sendWiFiData( CFDataGetBytePtr( data ), CFDataGetLength( data ) );
	}
	else if( CFEqual( inCommand, CFSTR( kAirPlayCommand_RequestViewArea ) ) )
	{
		int index = (int)CFDictionaryGetInt64( inParams, CFSTR( kAirPlayKey_ViewAreaIndex ), NULL);
		app_ulog( kLogLevelNotice, "Received RequestViewArea change %u\n", index );
	}
	else if( CFEqual( inCommand, CFSTR( kAirPlayCommand_ChangeUIContext ) ) )
	{
		CFStringRef url = (CFStringRef)CFDictionaryGetValue( inParams, CFSTR( kAirPlayKey_ContextURL ) );
		const char *urlStr = NULL;
	
		if( url ) {
			urlStr = CFStringGetCStringPtr( url, kCFStringEncodingUTF8 );
			app_ulog( kLogLevelNotice, "Received UI context change for %s\n", urlStr );
		}
	}
    else
    {
		const char *commond = NULL;

		commond = CFStringGetCStringPtr( inCommand, kCFStringEncodingUTF8 );
		app_ulog( kLogLevelNotice, "Unsupported session control request: %s\n", commond );
        err = kNotHandledErr;
    }
	
	return( err );
}
	
static void
	_AirPlayHandleSetEnhancedSiriParams(
		AirPlayReceiverSessionRef	inSession,
		AirPlayVoiceActivationMode	inMode,
		CFStringRef					inLanguage,
		void *						inContext )
{
	(void) inMode;
	(void) inLanguage;
	(void) inContext;

	// Start the AuxIn buffering for the plugin
	AirPlayReceiverSessionAuxInStart( inSession, NULL );
}

static void
	_AirPlayHandleSuggestUI(
		AirPlayReceiverSessionRef 	inSession,
		CFArrayRef			inURLs,
		void *				inContext )
{
	CFRange range = CFRangeMake( 0, CFArrayGetCount( inURLs ) );

	if( CFArrayContainsValue( inURLs, range, CFSTR( kAirPlayKey_ScreenURLsInstrumentCluster ) ) )
	{
		CFStringRef url;
		char compassQuery[32];
		char etaQuery[32];
		char speedLimitQuery[32];

		snprintf( compassQuery, sizeof( compassQuery ), "%s=%s", kAirPlayKey_ScreenURLsInstrumentClusterOptionShowCompass, "user" );
		snprintf( etaQuery, sizeof( etaQuery ), "%s=%s", kAirPlayKey_ScreenURLsInstrumentClusterOptionShowETA, "user" );
		snprintf( speedLimitQuery, sizeof( speedLimitQuery ), "%s=%s", kAirPlayKey_ScreenURLsInstrumentClusterOptionShowCompass, "user" );

		url = CFStringCreateWithFormat( kCFAllocatorDefault, NULL, CFSTR( "%s?%s&%s&%s"), kAirPlayKey_ScreenURLsInstrumentCluster, compassQuery, etaQuery, speedLimitQuery );
		if( url )
		{
			AirPlayReceiverSessionShowUI( inSession, kAltScreenUUID, url, NULL, NULL );
			CFRelease( url );
		}
	}
	else
	{
		AirPlayReceiverSessionStopUI( inSession, kAltScreenUUID, NULL, NULL );
	}
}

static void
    _AirPlayHandleDuckAudio(
	    AirPlayReceiverSessionRef	inSession,
	    double						inDurationSecs,
	    double						inVolume,
	    void *						inContext )
{
	(void) inContext;
    (void) inSession;

	app_ulog( kLogLevelNotice, "AirPlayHandleDuckAudio DurationSecs: %f, Volume: %f.\n", inDurationSecs, inVolume );
	
	caplayDuckAudioCB(inDurationSecs,inVolume);
//	handleSessionDuckAudio( inDurationSecs, inVolume );
}

static void
    _AirPlayHandleunDuckAudio(
	    AirPlayReceiverSessionRef	inSession,
	    double						inDurationSecs,
	    void *						inContext )
{
	(void) inContext;
    (void) inSession;

	app_ulog( kLogLevelNotice, "AirPlayHandleunDuckAudio DurationSecs: %f.\n", inDurationSecs );
	caplayUnduckAudioCB(inDurationSecs);
//	handleSessionUnduckAudio( inDurationSecs );
}

OSStatus _setupHIDDevices( void )
{
	// Assign a unique identifier for each HID device
	if( gHiFiTouch || gLoFiTouch )
	{
		gTouchUID = gNextDeviceUID++;
	}

	if( gKnob )
	{
		gKnobUID = gNextDeviceUID++;
	}

	if( gProxSensor )
	{
		gProxSensorUID = gNextDeviceUID++;
	}

	if( gTelephony )
	{
        gTelephonyUID = gNextDeviceUID++;
    }

	if ( gMediaButtons )
	{
		gMediaButtonsUID = gNextDeviceUID++;
	}

	return( kNoErr );
}

//===========================================================================================================================
//  forceKeyFrame
//===========================================================================================================================

static OSStatus _forceKeyFrame() {
    OSStatus    err;

    if (gAirPlaySession) {
        app_ulog(kLogLevelNotice, "_forceKeyFrame \n");
        err = AirPlayReceiverSessionForceKeyFrame(gAirPlaySession, kMainScreenUUID, NULL, NULL);
        require_noerr(err, exit);
    }

exit:
    return (err);
}

//===========================================================================================================================
//	_touchScreenUpdate
//===========================================================================================================================

static OSStatus _touchScreenUpdate( uint8_t inPress, uint16_t inX, uint16_t inY )
{
	OSStatus		err;
	uint8_t			report[ 5 ];
	
	app_ulog( kLogLevelNotice, "TouchScreenUpdate Press: %d ( %d, %d )\n", inPress, inX, inY );
	
	HIDTouchScreenFillReport( report, inPress, inX, inY );
	
	AirPlayReceiverSessionSendHIDReport( gAirPlaySession, gTouchUID, report, sizeof( report ) );
	require_noerr( err, exit );
	
exit:
	return( err );
}

//===========================================================================================================================
//	_telephonyUpdate
//===========================================================================================================================

static OSStatus _telephonyUpdate( int inButtonIndex )
{
	app_ulog( kLogLevelNotice, "HIDTelephony buttonIndex: %d\n", inButtonIndex );
	OSStatus		err;
	uint8_t			report[ 1 ];

	HIDTelephonyFillReport( report, inButtonIndex );

	AirPlayReceiverSessionSendHIDReport( gAirPlaySession, gTelephonyUID, report, sizeof( report ) );
	require_noerr( err, exit );

exit:
	return( err );
}

//===========================================================================================================================
//	_mediaButtonsUpdate
//===========================================================================================================================

static OSStatus _mediaButtonsUpdate( int inButtonIndex )
{
	app_ulog( kLogLevelNotice, "mediaButtonsUpdate buttonIndex %d\n", inButtonIndex );
	OSStatus		err;
	uint8_t			report[ 1 ];

	HIDMediaButtonsFillReport( report, inButtonIndex );

	AirPlayReceiverSessionSendHIDReport( gAirPlaySession, gMediaButtonsUID, report, sizeof( report ) );
	require_noerr( err, exit );

exit:
	return( err );
}

//===========================================================================================================================
//	KnobUpdate
//===========================================================================================================================

static double TranslateValue( double inValue, double inOldMin, double inOldMax, double inNewMin, double inNewMax )
{
	return( ( ( ( inValue - inOldMin ) / ( inOldMax - inOldMin ) ) * ( inNewMax - inNewMin ) ) + inNewMin );
}

OSStatus	KnobUpdate( void )
{
	OSStatus		err;
	uint8_t			report[ 4 ];
	int8_t			x;
	int8_t			y;
	
	// Normalize X and Y values to integers between -127 and 127
	x = (int8_t) TranslateValue( gXPosition, kMinXPosition, kMaxXPosition, -127, 127 );
	y = (int8_t) TranslateValue( gYPosition, kMinYPosition, kMinYPosition, -127, 127 );
	
	// A HIDKnobFillReport must be sent on both button pressed and button released events
	HIDKnobFillReport( report, gSelectButtonPressed, gHomeButtonPressed, gBackButtonPressed, x, y, gWheelPositionRelative );
	
	AirPlayReceiverSessionSendHIDReport( gAirPlaySession, gKnobUID, report, sizeof( report ) );
	require_noerr( err, exit );
	
exit:
	return( err );
}

//===========================================================================================================================
//	_proxSensorUpdate
//===========================================================================================================================

static OSStatus _proxSensorUpdate( bool inProxSensorPresence )
{
	OSStatus		err;
	uint8_t			report[ 1 ];
	
	HIDProximityFillReport( report, inProxSensorPresence );
	
	AirPlayReceiverSessionSendHIDReport( gAirPlaySession, gProxSensorUID, report, sizeof( report ) );
	require_noerr( err, exit );
	
exit:
	return( err );
}

//===========================================================================================================================
//	_ParseModes
//===========================================================================================================================

static OSStatus	_ParseModes( const char *inArg)
{
	app_ulog( kLogLevelNotice, "AirPlay starting _ParseModes = %s  .\n",inArg);

	OSStatus			err;
	const char *		token;
	size_t				len;
	
	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != '=' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if( ( strnicmpx( token, len, kAirPlayResourceIDString_MainScreen ) == 0 ) ||
		( strnicmpx( token, len, "screen" ) == 0 ) )
	{
		err = _ParseModeResource( &gInitialModesRaw.screen, inArg );
		require_noerr_quiet( err, exit );
	}
	else if( strnicmpx( token, len, kAirPlayResourceIDString_MainAudio ) == 0 )
	{
		err = _ParseModeResource( &gInitialModesRaw.mainAudio, inArg );
		require_noerr_quiet( err, exit );
	}
	else if( strnicmpx( token, len, kAirPlayAppStateIDString_PhoneCall ) == 0 )
	{
		gInitialModesRaw.phoneCall = kAirPlayTriState_True;
	}
	else if( strnicmpx( token, len, kAirPlayAppStateIDString_Speech ) == 0 )
	{
		if( stricmp( inArg, kAirPlaySpeechModeString_None ) == 0 )
		{
			gInitialModesRaw.speech = kAirPlaySpeechMode_None;
		}
		else if( stricmp( inArg, kAirPlaySpeechModeString_Speaking ) == 0 )
		{
			gInitialModesRaw.speech = kAirPlaySpeechMode_Speaking;
		}
		else if( stricmp( inArg, kAirPlaySpeechModeString_Recognizing ) == 0 )
		{
			gInitialModesRaw.speech = kAirPlaySpeechMode_Recognizing;
		}
		else
		{
			err = kParamErr;
			goto exit;
		}
	}
	else if( strnicmpx( token, len, kAirPlayAppStateIDString_TurnByTurn ) == 0 )
	{
		gInitialModesRaw.turnByTurn = kAirPlayTriState_True;
	}
	else
	{
		err = kParamErr;
		goto exit;
	}
	err = kNoErr;
	
exit:
	if( err ) fprintf( stderr, "error: bad mode '%s'\n", inArg );
	return( err );
}

//===========================================================================================================================
//	_ParseModeResource
//===========================================================================================================================

static OSStatus	_ParseModeResource( AirPlayResourceChange *inResource, const char *inArg )
{
	app_ulog( kLogLevelNotice, "AirPlay starting _ParseModeResource = %s  .\n",inArg);
	OSStatus			err;
	const char *		token;
	size_t				len;
	
	inResource->type		= kAirPlayTransferType_Take;
	inResource->priority	= kAirPlayTransferPriority_UserInitiated;

	// borrowID

	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != ',' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if( strnicmpx( token, len, kAirPlayBorrowID_NativeVR ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_NativeVR;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_BackupCamera ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_BackupCamera;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_PhoneCall ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_PhoneCall;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_UINotification ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_UINotification;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_Standby ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_Standby;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_DisplayOff ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_DisplayOff;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_AudioOff ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_AudioOff;
	}
	else
	{
		inResource->borrowID = NULL;
	}

	// type

	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != ',' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if( strnicmpx( token, len, kAirPlayTransferTypeString_NotApplicable ) == 0 )
	{
		inResource->type = kAirPlayTransferType_NotApplicable;
	}
	else if( strnicmpx( token, len, kAirPlayTransferTypeString_Take ) == 0 )
	{
		inResource->type = kAirPlayTransferType_Take;
	}
	else if( strnicmpx( token, len, kAirPlayTransferTypeString_Untake ) == 0 )
	{
		inResource->type = kAirPlayTransferType_Untake;
	}
	else if( strnicmpx( token, len, kAirPlayTransferTypeString_Borrow ) == 0 )
	{
		inResource->type = kAirPlayTransferType_Borrow;
	}
	else if( strnicmpx( token, len, kAirPlayTransferTypeString_Unborrow ) == 0 )
	{
		inResource->type = kAirPlayTransferType_Unborrow;
	}
	else
	{
		err = kParamErr;
		goto exit;
	}

	// priority

	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != ',' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if( strnicmpx( token, len, kAirPlayTransferPriorityString_NotApplicable ) == 0 )
	{
		inResource->priority = kAirPlayTransferPriority_NotApplicable;
	}
	else if( strnicmpx( token, len, kAirPlayTransferPriorityString_NiceToHave ) == 0 )
	{
		inResource->priority = kAirPlayTransferPriority_NiceToHave;
	}
	else if( strnicmpx( token, len, kAirPlayTransferPriorityString_UserInitiated ) == 0 )
	{
		inResource->priority = kAirPlayTransferPriority_UserInitiated;
	}
	else
	{
		err = kParamErr;
		goto exit;
	}
	
	// TakeConstraint
	
	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != ',' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if(      strnicmpx( token, len, kAirPlayConstraintString_Anytime ) == 0 )
	{
		inResource->takeConstraint = kAirPlayConstraint_Anytime;
	}
	else if( strnicmpx( token, len, kAirPlayConstraintString_UserInitiated ) == 0 )
	{
		inResource->takeConstraint = kAirPlayConstraint_UserInitiated;
	}
	else if( strnicmpx( token, len, kAirPlayConstraintString_Never ) == 0 )
	{
		inResource->takeConstraint = kAirPlayConstraint_Never;
	}
	else
	{
		err = kParamErr;
		goto exit;
	}
	
	// BorrowConstraint
	
	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != ',' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if(      strnicmpx( token, len, kAirPlayConstraintString_Anytime ) == 0 )
	{
		inResource->borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;
	}
	else if( strnicmpx( token, len, kAirPlayConstraintString_UserInitiated ) == 0 )
	{
		inResource->borrowOrUnborrowConstraint = kAirPlayConstraint_UserInitiated;
	}
	else if( strnicmpx( token, len, kAirPlayConstraintString_Never ) == 0 )
	{
		inResource->borrowOrUnborrowConstraint = kAirPlayConstraint_Never;
	}
	else
	{
		err = kParamErr;
		goto exit;
	}
	
	err = kNoErr;
	
exit:
	return( err );
}

//===========================================================================================================================
//	_initialModesCommand
//===========================================================================================================================

static void _initialModesCommand( const char * inCommand )
{
	char * 				offset = NULL;
	OSStatus			err;

	if( gAirPlaySession )
	{
		app_ulog_err( kLogLevelNotice, "AirPlaySession has already been started.\n" );
		return;
	}

	offset = strnstr( inCommand, "--night", strlen( inCommand ) );
	if( offset )	gNightMode = kCFBooleanTrue;

	offset = strnstr( inCommand, "screen", strlen( inCommand ) );
	_ParseModes( offset );

	offset = strnstr( inCommand, "mainAudio", strlen( inCommand ) );
	_ParseModes( offset );

	offset = strnstr( inCommand, "phoneCall", strlen( inCommand ) );
	_ParseModes( offset );

	offset = strnstr( inCommand, "speech", strlen( inCommand ) );
	_ParseModes( offset );

	offset = strnstr( inCommand, "turnByTurn", strlen( inCommand ) );
	_ParseModes( offset );

	gInitialModes = AirPlayCreateModesDictionary( &gInitialModesRaw, NULL, &err );
	check_noerr( err );

}

//===========================================================================================================================
//	_sendInitialChangeModeRequest
//===========================================================================================================================

static void	_sendInitialChangeModeRequest(
	const char *				inScreenBorrowID,
	AirPlayTransferType 		inScreenType,
	AirPlayTransferPriority		inScreenPriority,
	AirPlayConstraint			inScreenTake,
	AirPlayConstraint			inScreenBorrow,
	const char *				inAudioBorrowID,
	AirPlayTransferType 		inAudioType,
	AirPlayTransferPriority		inAudioPriority,
	AirPlayConstraint			inAudioTake,
	AirPlayConstraint			inAudioBorrow,
	AirPlayTriState				inPhone,
	AirPlaySpeechMode			inSpeech,
	AirPlayTriState				inTurnByTurn,
	AirPlayEntity				inScreenPermanentEntity,
	AirPlayConstraint			inScreenPermanentTakeConstraint,
	AirPlayConstraint			inScreenPermanentBorrowConstraint,
	AirPlayEntity				inAudioPermanentEntity,
	AirPlayConstraint			inAudioPermanentTakeConstraint,
	AirPlayConstraint			inAudioPermanentBorrowConstraint )
{
	// before the session is active, set initial modes.

	app_ulog( kLogLevelNotice, "\nInitial Mode (car -> iOS):"
		"\n - ScreenBorrowID: %s, ScreenType: %d, ScreenPriority: %d, ScreenTake: %d, ScreenBorrow: %d"
		"\n - AudioBorrowID : %s, AudioType : %d, AudioPriority : %d, AudioTake : %d, AudioBorrow : %d"
		"\n - Phone: %d, Speech: %d, TurnByTurn: %d"
		"\n - ScreenPermanentEntity: %d, ScreenPermanentTakeConstraint: %d, ScreenPermanentBorrowConstraint: %d"
		"\n - AudioPermanentEntity : %d, AudioPermanentTakeConstraint : %d, AudioPermanentBorrowConstraint : %d.\n",
		inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
		inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn,
		inScreenPermanentEntity, inScreenPermanentTakeConstraint, inScreenPermanentBorrowConstraint,
		inAudioPermanentEntity, inAudioPermanentTakeConstraint, inAudioPermanentBorrowConstraint );

	// Initial Mode (car -> iOS)

	_setChangeModesStruct( &gInitialModesRaw, inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
		inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn );

	gInitialPermanentEntity.permanentEntityScreen = inScreenPermanentEntity;
	gInitialPermanentEntity.screen.takeConstraint = inScreenPermanentTakeConstraint;
	gInitialPermanentEntity.screen.borrowOrUnborrowConstraint = inScreenPermanentBorrowConstraint;

	gInitialPermanentEntity.permanentEntityMainAudio = inAudioPermanentEntity;
	gInitialPermanentEntity.mainAudio.takeConstraint = inAudioPermanentTakeConstraint;
	gInitialPermanentEntity.mainAudio.borrowOrUnborrowConstraint = inAudioPermanentBorrowConstraint;

	gInitialModes = AirPlayCreateInitialModesDictionary( &gInitialPermanentEntity, &gInitialModesRaw, NULL, NULL );
}

//===========================================================================================================================
//	_sendGenericChangeModeRequest
//===========================================================================================================================

static void	_sendGenericChangeModeRequest(
	const char *				inScreenBorrowID,
	AirPlayTransferType 		inScreenType,
	AirPlayTransferPriority		inScreenPriority,
	AirPlayConstraint			inScreenTake,
	AirPlayConstraint			inScreenBorrow,
	const char *				inAudioBorrowID,
	AirPlayTransferType 		inAudioType,
	AirPlayTransferPriority		inAudioPriority,
	AirPlayConstraint			inAudioTake,
	AirPlayConstraint			inAudioBorrow,
	AirPlayTriState				inPhone,
	AirPlaySpeechMode			inSpeech,
	AirPlayTriState				inTurnByTurn )
{
	AirPlayModeChanges		changes;
	
	//when the Airplay session is active
	if( gAirPlaySession)
	{
		app_ulog( kLogLevelNotice, "\nChange Mode (car -> iOS):"
			"\n - ScreenBorrowID: %s, ScreenType: %d, ScreenPriority: %d, ScreenTake: %d, ScreenBorrow: %d"
			"\n - AudioBorrowID : %s, AudioType : %d, AudioPriority : %d, AudioTake : %d, AudioBorrow : %d"
			"\n - Phone: %d, Speech: %d, TurnByTurn: %d.\n", inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
			inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn  );
		// changeModes (car -> iOS)
		_setChangeModesStruct( &changes, inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
			inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn );
		AirPlayReceiverSessionChangeModes( gAirPlaySession, &changes, NULL, NULL, NULL );
	}

}

//===========================================================================================================================
//	_setChangeModesStruct
//===========================================================================================================================

static void	_setChangeModesStruct(
	AirPlayModeChanges	* 		outModeChanges,
	const char *				inScreenBorrowID,
	AirPlayTransferType 		inScreenType,
	AirPlayTransferPriority		inScreenPriority,
	AirPlayConstraint			inScreenTake,
	AirPlayConstraint			inScreenBorrow,
	const char *				inAudioBorrowID,
	AirPlayTransferType 		inAudioType,
	AirPlayTransferPriority		inAudioPriority,
	AirPlayConstraint			inAudioTake,
	AirPlayConstraint			inAudioBorrow,
	AirPlayTriState				inPhone,
	AirPlaySpeechMode			inSpeech,
	AirPlayTriState				inTurnByTurn )
{
	if( !outModeChanges ) return;

	outModeChanges->screen.borrowID							= inScreenBorrowID;
	outModeChanges->screen.type								= inScreenType;
	outModeChanges->screen.priority							= inScreenPriority;
	outModeChanges->screen.takeConstraint 					= inScreenTake;
	outModeChanges->screen.borrowOrUnborrowConstraint 		= inScreenBorrow;
	outModeChanges->mainAudio.borrowID						= inAudioBorrowID;
	outModeChanges->mainAudio.type							= inAudioType;
	outModeChanges->mainAudio.priority						= inAudioPriority;
	outModeChanges->mainAudio.takeConstraint 				= inAudioTake;
	outModeChanges->mainAudio.borrowOrUnborrowConstraint 	= inAudioBorrow;
	outModeChanges->phoneCall 								= inPhone;
	outModeChanges->speech 									= inSpeech;
	outModeChanges->turnByTurn 								= inTurnByTurn;
}

//===========================================================================================================================
//	_changeResourceMode
//===========================================================================================================================

static void _changeResourceMode(
		const char *								inBorrowID,
		AirPlayResourceID							inResourceID,
		AirPlayTransferType							inType,
		AirPlayTransferPriority						inPriority,
		AirPlayConstraint							inTakeConstraint,
		AirPlayConstraint							inBorrowOrUnborrowConstraint)
{
	OSStatus 	err;
	CFStringRef borrowID = NULL;

	app_ulog( kLogLevelNotice, "\nChange Resource Mode (car -> iOS):"
		"\n - ResourceID: %d, BorrowID: %s, Type: %d, Priority: %d, TakeConstraint: %d, BorrowOrUnborrowConstraint: %d\n", 
		inResourceID, inBorrowID, inType, inPriority, inTakeConstraint, inBorrowOrUnborrowConstraint );

	borrowID = CFStringCreateWithCString( kCFAllocatorDefault, inBorrowID, kCFStringEncodingUTF8 );
	require_action( borrowID, exit, err = kNoMemoryErr );

	err = AirPlayReceiverSessionChangeResourceMode( gAirPlaySession, inResourceID, inType,
				inPriority, inTakeConstraint, inBorrowOrUnborrowConstraint, borrowID, NULL, NULL, NULL );
	require_noerr( err, exit );

exit:
	CFReleaseNullSafe( borrowID );
}

//===========================================================================================================================
//	_changeAppState
//===========================================================================================================================

static void _changeAppState(
		AirPlaySpeechMode							inSpeechMode,
		AirPlayTriState								inPhoneCall,
		AirPlayTriState								inTurnByTurn)
{
	app_ulog( kLogLevelNotice, "\nChange App Mode (car -> iOS):"
		"\n - Phone: %d, Speech: %d, TurnByTurn: %d.\n", inPhoneCall, inSpeechMode, inTurnByTurn );

	if (gAirPlaySession)	AirPlayReceiverSessionChangeAppState( gAirPlaySession, inSpeechMode, inPhoneCall, inTurnByTurn, NULL, NULL, NULL );
}

//===========================================================================================================================
//	_requestUI
//===========================================================================================================================

static void _requestUI()
{
	app_ulog( kLogLevelNotice, "requestUI.\n" );

	if (gAirPlaySession)	AirPlayReceiverSessionRequestUI( gAirPlaySession, NULL, NULL, NULL );
}

static void _requestUIWithURL( const char * url )
{
    app_ulog( kLogLevelNotice, "requestUI with URL %s\n", url );

    if (gAirPlaySession)	AirPlayReceiverSessionRequestUI( gAirPlaySession, CFStringCreateWithCString( kCFAllocatorDefault, url, kCFStringEncodingUTF8 ), NULL, NULL );
}

//===========================================================================================================================
//	_requestSiriAction
//===========================================================================================================================

static void _requestSiriAction( AirPlaySiriAction	inAction )
{
	app_ulog( kLogLevelNotice, "requestSiriAction Action: %d\n", inAction );
	int siriButton = 0;
	if( inAction == 1 ){
		siriButton = 2;
	}else if( inAction == 0 )
	{
		siriButton = 3;
	}

	if (gAirPlaySession)	AirPlayReceiverSessionRequestSiriAction( gAirPlaySession, siriButton, NULL, NULL );
}

//===========================================================================================================================
//	_setNightMode
//===========================================================================================================================

static void _setNightMode(int mode)
{
	app_ulog( kLogLevelNotice, "set night mode: %d.\n", mode );

	if( mode == 1 )	gNightMode = kCFLBooleanTrue;
	if( mode == 0 )	gNightMode = kCFLBooleanFalse;

	if (gAirPlaySession)	AirPlayReceiverSessionSetNightMode( gAirPlaySession, mode, NULL, NULL );
}

//===========================================================================================================================
//	_sendUIAppearanceUpdate
//===========================================================================================================================

static void _UIAppearanceUpdate( int inMode, int inSetting )
{
	app_ulog( kLogLevelNotice, "sends an UI appearance update mode: %d, setting: %d.\n", inMode, inSetting );

	gAppearanceMode = inMode;

	if( inSetting )	gAppearanceSetting = kAirPlayAppearanceSetting_Automatic;
	else			gAppearanceSetting = kAirPlayAppearanceSetting_UserChoice;

	if( gAirPlaySession )	AirPlayReceiverSessionUIAppearanceUpdate( gAirPlaySession, kMainScreenUUID, inMode, gAppearanceSetting, NULL, NULL );
}

//===========================================================================================================================
//	_setLimitedUI
//===========================================================================================================================

static void _setLimitedUI( int inLimitUI )
{
	app_ulog( kLogLevelNotice, "set limited UI: %d.\n", inLimitUI );

	if( inLimitUI == 1 )	gLimitedUI = kCFLBooleanTrue;
	if( inLimitUI == 0 )	gLimitedUI = kCFLBooleanFalse;

	if (gAirPlaySession)	AirPlayReceiverSessionSetLimitedUI( gAirPlaySession, inLimitUI, NULL, NULL );
}

//===========================================================================================================================
//	_sendETCUpdate
//===========================================================================================================================

static OSStatus _sendETCUpdate(bool inETCEnabled)
{
	OSStatus err;
	CFMutableDictionaryRef etcDict = NULL;
	CFMutableDictionaryRef vehicleDict = NULL;

	etcDict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
	require_action( etcDict, exit, err = kNoMemoryErr );
	CFDictionarySetBoolean( etcDict, CFSTR( kAirPlayKey_Active), inETCEnabled );

	vehicleDict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
	require_action( vehicleDict, exit, err = kNoMemoryErr );
	CFDictionarySetValue( vehicleDict, CFSTR( kAirPlayVehicleInformation_ETC), etcDict );

	err = AirPlayReceiverSessionUpdateVehicleInformation( gAirPlaySession, vehicleDict, NULL, NULL );
	require_noerr( err, exit );

exit:
	CFReleaseNullSafe( etcDict );
	CFReleaseNullSafe( vehicleDict );
	return( err );
}

//===========================================================================================================================
//	_getAudioFormats
//===========================================================================================================================

static CFArrayRef _getAudioFormats( OSStatus *outErr )
{
	CFArrayRef					dictArray = NULL;
	OSStatus					err;
	AirPlayAudioFormat			inputFormats, outputFormats;
	
	// Main Audio - Compatibility
//	inputFormats =
//		kAirPlayAudioFormat_PCM_8KHz_16Bit_Mono |
//		kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
//		kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono;
//	outputFormats =
//		kAirPlayAudioFormat_PCM_8KHz_16Bit_Mono |
//		kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
//		kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
//		// kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
//		kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo;
//	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Compatibility, inputFormats, outputFormats );
//	require_noerr( err, exit );
	
	// Main Audio - Alert
	inputFormats = 0;
	outputFormats =
			kAirPlayAudioFormat_OPUS_48KHz_Mono;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Alert, inputFormats, outputFormats );
	require_noerr( err, exit );
	
	// Main Audio - Default
	inputFormats =
		kAirPlayAudioFormat_OPUS_16KHz_Mono;
	outputFormats =
		kAirPlayAudioFormat_OPUS_16KHz_Mono;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Default, inputFormats, outputFormats );
	require_noerr( err, exit );
	
//	// Main Audio - Media
//	inputFormats = 0;
//	outputFormats =
//		//kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
//		kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo;
//	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Media, inputFormats, outputFormats );
//	require_noerr( err, exit );
	
	// Main Audio - Telephony
	inputFormats =
		kAirPlayAudioFormat_OPUS_16KHz_Mono;
	outputFormats =
		kAirPlayAudioFormat_OPUS_16KHz_Mono;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Telephony, inputFormats, outputFormats );
	require_noerr( err, exit );
	
	// Main Audio - SpeechRecognition
	inputFormats =
		kAirPlayAudioFormat_OPUS_16KHz_Mono;
	outputFormats =
		kAirPlayAudioFormat_OPUS_16KHz_Mono;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_SpeechRecognition, inputFormats, outputFormats );
	require_noerr( err, exit );
	
	// Alt Audio - Default
	inputFormats = 0;
	outputFormats =
			kAirPlayAudioFormat_OPUS_48KHz_Mono;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Default, inputFormats, outputFormats );
	require_noerr( err, exit );

	// Alt Audio - Compatibility
//	inputFormats = 0;
//	outputFormats =
//		// kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
//		kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo;
//	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Compatibility, inputFormats, outputFormats );
//	require_noerr( err, exit );

	// Main High - Media
	inputFormats = 0;
	outputFormats =
		kAirPlayAudioFormat_AAC_LC_48KHz_Stereo;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainHighAudio, kAudioStreamAudioType_Media, inputFormats, outputFormats );
	require_noerr( err, exit );
	
	if( gSupportsEnhancedSiri )
	{
		// Aux Out - speechRecognition
		inputFormats = 0;
		outputFormats =
			kAirPlayAudioFormat_PCM_48KHz_16Bit_Mono;
			//kAirPlayAudioFormat_OPUS_48KHz_Mono;
		err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_AuxOutAudio, kAudioStreamAudioType_SpeechRecognition, inputFormats, outputFormats );
		require_noerr( err, exit );
		
		// Aux In - speechRecognition
		outputFormats = 0;
		inputFormats =
			kAirPlayAudioFormat_OPUS_16KHz_Mono;
		err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_AuxInAudio, kAudioStreamAudioType_SpeechRecognition, inputFormats, outputFormats );
		require_noerr( err, exit );
	}
	
exit:
	if( outErr ) *outErr = err;
	return( dictArray );
}

//===========================================================================================================================
//	_getAudioLatencies
//===========================================================================================================================

static CFArrayRef _getAudioLatencies( OSStatus *outErr )
{
	CFArrayRef							dictArray = NULL;
	OSStatus							err;
	
	// $$$ TODO: obtain audio latencies for all audio formats and audio types supported by the underlying hardware.
	// Audio latencies are reported as an ordered array of dictionaries (from least restrictive to the most restrictive).
	// Each dictionary contains the following keys:
	//		[kAudioSessionKey_Type] - if not specified, then latencies are good for all stream types
	//		[kAudioSessionKey_AudioType] - if not specified, then latencies are good for all audio types
	//		[kAudioSessionKey_SampleRate] - if not specified, then latencies are good for all sample rates
	//		[kAudioSessionKey_SampleSize] - if not specified, then latencies are good for all sample sizes
	//		[kAudioSessionKey_Channels] - if not specified, then latencies are good for all channel counts
	//		[kAudioSessionKey_CompressionType] - if not specified, then latencies are good for all compression types
	//		kAudioSessionKey_InputLatencyMicros
	//		kAudioSessionKey_OutputLatencyMicros

	
	
	// MainAudio default latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Default, 16000, 16, 1, 214000, 72000 );
    require_noerr( err, exit );

//    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Default, 24000, 16, 1, 214000, 72000 );
//    require_noerr( err, exit );

    //电话
	// MainAudio telephony latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Telephony, 16000, 16, 1, 98000, 72000 );
    require_noerr( err, exit );

    // MainAudio telephony latencies - set 0 latencies for now - $$$ TODO set real latencies
//    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Telephony, 24000, 16, 1, 98000, 72000 );
//    require_noerr( err, exit );
	

    //语音
	// MainAudio SpeechRecognition latencies - set 0 latencies for now - $$$ TODO set real latencies
	err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_SpeechRecognition, 16000, 16, 1, 160000, 40000 );
	require_noerr( err, exit );

//	err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_SpeechRecognition, 24000, 16, 1, 160000, 40000 );
//	require_noerr( err, exit );
	
	//导航
	// Main Audio alert latencies - set 0 latencies for now - $$$ TODO set real latencies
	err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Alert, 48000, 16, 1, 0, 214000 );
	require_noerr( err, exit );
	
	//媒体
	// MainHighAudio Media latencies (wireless only) - set 0 latencies for now - $$$ TODO set real latencies
	err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainHighAudio, kAudioStreamAudioType_Media, 48000, 16, 2, 0, 214000 );
	require_noerr( err, exit );

	// 按键音
	// AltAudio default latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Default, 48000, 16, 1, 0, 98000 );
    require_noerr( err, exit );

exit:
	if( outErr ) *outErr = kNoErr;
	return( dictArray );
}

//===========================================================================================================================
//	GetHIDDevices
//===========================================================================================================================

static CFArrayRef _getHIDDevices( OSStatus *outErr )
{
	CFArrayRef					dictArray = NULL;
	OSStatus					err;

	if( gHiFiTouch || gLoFiTouch )
	{
		uint8_t *	descPtr;
		size_t		descLen;

		err = HIDTouchScreenSingleCreateDescriptor( &descPtr, &descLen, gVideoWidth, gVideoHeight );
		require_noerr( err, exit );

		err = AirPlayInfoArrayAddHIDDevice( &dictArray, gTouchUID, "Touch Screen", kUSBVendorTouchScreen, kUSBProductTouchScreen, kUSBCountryCodeUnused, descPtr, descLen, kMainScreenUUID );
		free( descPtr );
		require_noerr( err, exit );
	}

	if( gKnob )
	{
		uint8_t *	descPtr;
		size_t		descLen;

		err = HIDKnobCreateDescriptor( &descPtr, &descLen );
		require_noerr( err, exit );

		err = AirPlayInfoArrayAddHIDDevice( &dictArray, gKnobUID, "Knob & Buttons", kUSBVendorKnobButtons, kUSBProductKnobButtons, kUSBCountryCodeUnused, descPtr, descLen, kMainScreenUUID );
		free( descPtr );
		require_noerr( err, exit );
	}

	if( gProxSensor )
	{
		uint8_t *	descPtr;
		size_t		descLen;

		err = HIDProximityCreateDescriptor( &descPtr, &descLen );
		require_noerr( err, exit );

		err = AirPlayInfoArrayAddHIDDevice( &dictArray, gProxSensorUID, "Proximity Sensor", kUSBVendorProxSensor, kUSBProductProxSensor, kUSBCountryCodeUnused, descPtr, descLen, kMainScreenUUID );
		free( descPtr );
		require_noerr( err, exit );
	}

	if ( gTelephony )
	{
		uint8_t *	descPtr;
		size_t		descLen;

		err = HIDTelephonyCreateDescriptor( &descPtr, &descLen );
		require_noerr( err, exit );

		err = AirPlayInfoArrayAddHIDDevice( &dictArray, gTelephonyUID, "Telephony Key", kUSBVendorTelephony, kUSBProductTelephony, kUSBCountryCodeUS, descPtr, descLen, kDefaultUUID );
		free( descPtr );
		require_noerr( err, exit );
	}

	if ( gMediaButtons )
	{
		uint8_t *	descPtr;
		size_t		descLen;

		err = HIDMediaButtonsCreateDescriptor( &descPtr, &descLen );
		require_noerr( err, exit );

		err = AirPlayInfoArrayAddHIDDevice( &dictArray, gMediaButtonsUID, "Media Buttons", kUSBVendorMediaButtons, kUSBProductMediaButtons, kUSBCountryCodeUS, descPtr, descLen, kDefaultUUID );
		free( descPtr );
		require_noerr( err, exit );
	}

	err = kNoErr;

exit:
	if( outErr ) *outErr = err;
	return( dictArray );
}

//===========================================================================================================================
//	_getScreenDisplays
//===========================================================================================================================

static CFArrayRef _getScreenDisplays( OSStatus *outErr )
{
	OSStatus err;

	uint32_t mainWidthPixels = gVideoWidth;
	uint32_t mainHeightPixels = gVideoHeight;
	uint32_t mainWidthMM = gWidthPhysical;
	uint32_t mainHeightMM = gHeightPhysical;
	uint32_t mainFPS = 30;

	uint32_t altWidthPixels = 800;
	uint32_t altHeightPixels = 480;
	uint32_t altWidthMM = 175;
	uint32_t altHeightMM = 105;
	uint32_t altFPS = 30;

	CFArrayRef dictArray = NULL;
	CFMutableDictionaryRef screenDict = NULL;
	uint32_t u32 = 0;

	uint32_t viewAreaCount = 4;
	uint32_t initialViewArea = 0;
	AirPlayScreenArea activeAreas[4];
	uint32_t adjacentAreas[2];
	uint32_t adjacentAreaCount;

	if( gKnob )			u32 |= kAirPlayDisplayFeatures_Knobs;
	if( gHiFiTouch )	u32 |= kAirPlayDisplayFeatures_HighFidelityTouch;
	if( gLoFiTouch )	u32 |= kAirPlayDisplayFeatures_LowFidelityTouch;
	if( gTouchpad )		u32 |= kAirPlayDisplayFeatures_Touchpad;

	// Full screen 1920x720 @ (0x0)
	activeAreas[0].width = 1920;
	activeAreas[0].height = 720;
	activeAreas[0].originX = 0;
	activeAreas[0].originY = 0;
	activeAreas[0].mainScreenProperties.viewAreaTransitionControl = false;
	activeAreas[0].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
	activeAreas[0].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
	activeAreas[0].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Left screen 1400x720 @ (0,0)
	activeAreas[1].width = 1400;
	activeAreas[1].height = 720;
	activeAreas[1].originX = 0;
	activeAreas[1].originY = 0;
	activeAreas[1].mainScreenProperties.viewAreaTransitionControl = false;
	activeAreas[1].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
	activeAreas[1].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
	activeAreas[1].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Right screen 1400x720 @ (520,0)
	activeAreas[2].width = 1400;
	activeAreas[2].height = 720;
	activeAreas[2].originX = 520;
	activeAreas[2].originY = 0;
	activeAreas[2].mainScreenProperties.viewAreaTransitionControl = false;
	activeAreas[2].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
	activeAreas[2].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
	activeAreas[2].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Middle screen 1400x600 @ (140,60)
	activeAreas[3].width = 1400;
	activeAreas[3].height = 600;
	activeAreas[3].originX = 260;
	activeAreas[3].originY = 60;
	activeAreas[3].mainScreenProperties.viewAreaTransitionControl = false;
	activeAreas[3].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
	activeAreas[3].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
	activeAreas[3].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Initial view area = activeArea[0]
	initialViewArea = 0;

	// Possible transitions to/from the initial view area activeArea[0]
	// activeArea[0] <-> activeArea[1]
	// activeArea[0] <-> activeArea[3]
	adjacentAreas[0] = 1;
	adjacentAreas[1] = 3;
	adjacentAreaCount = 2;

	// Create Main Screen Dictionary
	err = AirPlayMainScreenDictCreate( &screenDict, kMainScreenUUID, u32, gPrimaryInputDevice, mainFPS, mainWidthPixels, mainHeightPixels, mainWidthMM, mainHeightMM );
	require_noerr( err, exit );

	// Add View Areas
	// err = AirPlayScreenDictSetViewAreas( screenDict, initialViewArea, adjacentAreaCount, adjacentAreas, viewAreaCount, activeAreas, NULL );
	// require_noerr( err, exit );

	// Add Appearance options
	err = AirPlayScreenDictAddUIAppearance( screenDict, gAppearanceMode, gAppearanceSetting );
	require_noerr( err, exit );

	// err = AirPlayScreenDictAddMapAppearance( screenDict, kAirPlayAppearanceMode_Light, kAirPlayAppearanceSetting_Automatic );
	// require_noerr( err, exit );

	err = AirPlayInfoArrayAddScreen( &dictArray, screenDict );
	require_noerr( err, exit );
	CFRelease( screenDict );
	screenDict = NULL;

	// Instrument Cluster (show the map upon connect)
	// err = AirPlayAltScreenDictCreate( &screenDict, kAltScreenUUID, altFPS, altWidthPixels, altHeightPixels, altWidthMM, altHeightMM, CFSTR( kAirPlayKey_ScreenURLsInstrumentClusterMap ) );
	// require_noerr( err, exit );
	
	// Add Appearance option
	// err = AirPlayScreenDictAddMapAppearance( screenDict, kAirPlayAppearanceMode_Dark, kAirPlayAppearanceSetting_Always );
	// require_noerr( err, exit );
	
	// err = AirPlayInfoArrayAddScreen( &dictArray, screenDict );
	// require_noerr( err, exit );
	// CFRelease( screenDict );
	// screenDict = NULL;

	// Instrument Cluster (show the turn card upon connect)
	// err = AirPlayAltScreenDictCreate( &screenDict, kAltScreen2UUID, altFPS, altWidthPixels, altHeightPixels, altWidthMM, altHeightMM, CFSTR( kAirPlayKey_ScreenURLsInstrumentClusterInstructionCard ) );
	// require_noerr( err, exit );
	
	// Add Appearance option
	// err = AirPlayScreenDictAddMapAppearance( screenDict, kAirPlayAppearanceMode_Dark, kAirPlayAppearanceSetting_Always );
	// require_noerr( err, exit );
	
	// err = AirPlayInfoArrayAddScreen( &dictArray, screenDict );
	// require_noerr( err, exit );
	// CFRelease( screenDict );
	// screenDict = NULL;

exit:
	*outErr = err;
	CFReleaseNullSafe( screenDict );
	return( dictArray );
}

static void viewAreaChangeFullToLeft()
{
	uint32_t viewAreaIndex = 1;
	uint32_t adjacentAreas[2] = { 0, 2 };
	uint32_t adjacentAreaCount = 2;

	AirPlayReceiverSessionViewAreaUpdate( gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount, adjacentAreas, NULL, NULL );
}

static void viewAreaChangeLeftToRight()
{
	uint32_t viewAreaIndex = 2;
	uint32_t adjacentAreas[2] = { 1, 3 };
	uint32_t adjacentAreaCount = 2;

	AirPlayReceiverSessionViewAreaUpdate( gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount, adjacentAreas, NULL, NULL );
}

static void viewAreaChangeRightToMiddle()
{
	uint32_t viewAreaIndex = 3;
	uint32_t adjacentAreas[2] = { 2, 0 };
	uint32_t adjacentAreaCount = 2;

	AirPlayReceiverSessionViewAreaUpdate( gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount, adjacentAreas, NULL, NULL );
}

static void viewAreaChangeMiddleToFull()
{
	uint32_t viewAreaIndex = 0;
	uint32_t adjacentAreas[2] = { 3, 1 };
	uint32_t adjacentAreaCount = 2;

	AirPlayReceiverSessionViewAreaUpdate( gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount, adjacentAreas, NULL, NULL );
}

//===========================================================================================================================
//	_getScreenDisplaysWindowConfig
//===========================================================================================================================

static CFArrayRef _getScreenDisplaysWindowConfig( OSStatus *outErr )
{
	OSStatus err;

	uint32_t mainWidthPixels = 800;
	uint32_t mainHeightPixels = 1280;
	uint32_t mainWidthMM = 168;
	uint32_t mainHeightMM = 240;
	uint32_t mainFPS = 30;

	CFArrayRef dictArray = NULL;
	CFMutableDictionaryRef screenDict = NULL;
	uint32_t u32 = 0;

	uint32_t viewAreaCount = 2;
	uint32_t initialViewArea = 0;
	AirPlayScreenArea activeAreas[2];
	uint32_t adjacentAreas[1];
	uint32_t adjacentAreaCount;

	if( gKnob )			u32 |= kAirPlayDisplayFeatures_Knobs;
	if( gHiFiTouch )	u32 |= kAirPlayDisplayFeatures_HighFidelityTouch;
	if( gLoFiTouch )	u32 |= kAirPlayDisplayFeatures_LowFidelityTouch;
	if( gTouchpad )		u32 |= kAirPlayDisplayFeatures_Touchpad;

	// Full screen 800x1200 @ (0,80)
	activeAreas[0].width = 800;
	activeAreas[0].height = 1200;
	activeAreas[0].originX = 0;
	activeAreas[0].originY = 80;
	activeAreas[0].mainScreenProperties.viewAreaTransitionControl = true;
	activeAreas[0].mainScreenProperties.viewAreaSupportsFocusTransfer = true;
	activeAreas[0].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Bottom;
	activeAreas[0].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Left screen 800x480 @ (0,80)
	activeAreas[1].width = 800;
	activeAreas[1].height = 480;
	activeAreas[1].originX = 0;
	activeAreas[1].originY = 80;
	activeAreas[1].mainScreenProperties.viewAreaTransitionControl = true;
	activeAreas[1].mainScreenProperties.viewAreaSupportsFocusTransfer = true;
	activeAreas[1].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_DriveSide;
	activeAreas[1].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Initial view area = activeArea[0]
	initialViewArea = 0;

	// Possible transitions to/from the initial view area activeArea[0]
	// activeArea[0] <-> activeArea[1]
	adjacentAreaCount = 1;
	adjacentAreas[0] = 1;

	// Create Main Screen Dictionary
	err = AirPlayMainScreenDictCreate( &screenDict, kMainScreenUUID, u32, gPrimaryInputDevice, mainFPS, mainWidthPixels, mainHeightPixels, mainWidthMM, mainHeightMM );
	require_noerr( err, exit );

	// Add View Areas
	err = AirPlayScreenDictSetViewAreas( screenDict, initialViewArea, adjacentAreaCount, adjacentAreas, viewAreaCount, activeAreas, NULL );
	require_noerr( err, exit );

	// Support Corner Clipping Masks
	err = AirPlayScreenDictSetCornerMasksSupport( screenDict, true );
	require_noerr( err, exit );

	// Since this viewArea supports Focus Transfer, give controller focus at connect
	err = AirPlayScreenDictSetAccessoryGiveFocus( screenDict, false );

	err = AirPlayInfoArrayAddScreen( &dictArray, screenDict );
	require_noerr( err, exit );
	CFRelease( screenDict );
	screenDict = NULL;

exit:
	*outErr = err;
	CFReleaseNullSafe( screenDict );
	return( dictArray );
}

static CFDictionaryRef createInitialModesForBackupCamera()
{
	AirPlayModeChanges initialModes;
	AirPlayInitialPermanentEntity initialPerm;
	CFDictionaryRef dict;

	// Example for backup camera where the permanent entity is accessory
	AirPlayModeChangesInit( &initialModes );

	initialModes.speech = kAirPlaySpeechMode_None;
	initialModes.phoneCall = kAirPlayTriState_False;
	initialModes.turnByTurn = kAirPlayTriState_False;

	initialModes.screen.type = kAirPlayTransferType_Borrow;
	initialModes.screen.priority = kAirPlayTransferPriority_UserInitiated;
	initialModes.screen.takeConstraint = kAirPlayConstraint_NotApplicable;
	initialModes.screen.borrowOrUnborrowConstraint = kAirPlayConstraint_Never;
	initialModes.screen.borrowID = kAirPlayBorrowID_BackupCamera;

	initialModes.mainAudio.type = kAirPlayTransferType_Take;
	initialModes.mainAudio.priority = kAirPlayTransferPriority_UserInitiated;
	initialModes.mainAudio.takeConstraint = kAirPlayConstraint_UserInitiated;
	initialModes.mainAudio.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

    AirPlayInitialPermanentEntityInit( &initialPerm );

	initialPerm.permanentEntityScreen = kAirPlayEntity_Accessory;
	initialPerm.screen.takeConstraint = kAirPlayConstraint_UserInitiated;
	initialPerm.screen.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

	initialPerm.permanentEntityMainAudio = kAirPlayEntity_Accessory;
	initialPerm.mainAudio.takeConstraint = kAirPlayConstraint_UserInitiated;
	initialPerm.mainAudio.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

	dict = AirPlayCreateInitialModesDictionary( &initialPerm, &initialModes, CFSTR("Initial modes"), NULL);

	return dict;
}

static CFDictionaryRef createInitialModesForController( void )
{
	AirPlayModeChanges initialModes;
	AirPlayInitialPermanentEntity initialPerm;
	CFDictionaryRef dict;

	// Example for controller to have the resources
	AirPlayModeChangesInit( &initialModes );

	initialModes.speech = kAirPlaySpeechMode_None;
	initialModes.phoneCall = kAirPlayTriState_False;
	initialModes.turnByTurn = kAirPlayTriState_False;

	// For backwards compatability
	initialModes.screen.type = kAirPlayTransferType_Take;
	initialModes.screen.priority = kAirPlayTransferPriority_NiceToHave;
	initialModes.screen.takeConstraint = kAirPlayConstraint_Anytime;
	initialModes.screen.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

	initialModes.mainAudio.type = kAirPlayTransferType_Take;
	initialModes.mainAudio.priority = kAirPlayTransferPriority_NiceToHave;
	initialModes.mainAudio.takeConstraint = kAirPlayConstraint_Anytime;
	initialModes.mainAudio.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

    AirPlayInitialPermanentEntityInit( &initialPerm );

	initialPerm.permanentEntityScreen = kAirPlayEntity_Controller;
	initialPerm.permanentEntityMainAudio = kAirPlayEntity_Controller;

	dict = AirPlayCreateInitialModesDictionary( &initialPerm, &initialModes, CFSTR("Initial modes"), NULL);

	return dict;
}


int sendBluetoothRfcommData(int len,char * pData) {
//	app_ulog( kLogLevelNotice, "sendBluetoothRfcommData \n");
	return sendIAP2BluetoothRfcommData(len,pData);
}

int sendTouchScreenUpdate( uint8_t inPress, uint16_t inX, uint16_t inY ){
	app_ulog( kLogLevelNotice, "sendTouchScreenUpdate \n");
	return _touchScreenUpdate(inPress,inX,inY);
}

int sendKnobScreenUpdate(char selectButtonPressed, char homeButtonPressed, char backButtonPressed, double x,
                     double y, char wheelPositionRelative){
	gSelectButtonPressed = selectButtonPressed;
	gHomeButtonPressed = homeButtonPressed;
	gBackButtonPressed = backButtonPressed;
	gXPosition = x;
	gYPosition = y;
	gWheelPositionRelative = wheelPositionRelative;
	return KnobUpdate();
}

int sendWiFiInformation(const char * ssid,const char * password){
	strcpy(gWifiSsid,ssid);
	strcpy(gWifiPassword,password);
	return 0;
}

void sendCarPlayChangeModeRequest(
	const char *	inScreenBorrowID,
	int32_t 		inScreenType,
	int32_t		    inScreenPriority,
	int32_t			inScreenTake,
	int32_t			inScreenBorrow,
	const char *	inAudioBorrowID,
	int32_t 		inAudioType,
	int32_t		    inAudioPriority,
	int32_t			inAudioTake,
	int32_t			inAudioBorrow,
	int32_t		    inPhone,
	int32_t			inSpeech,
	int32_t			inTurnByTurn ){

	_sendGenericChangeModeRequest(inScreenBorrowID,inScreenType,inScreenPriority,
			inScreenTake,inScreenBorrow,inAudioBorrowID,inAudioType,inAudioPriority,
			inAudioTake,inAudioBorrow,inPhone,inSpeech,inTurnByTurn);
}

void sendCarPlayRequestUI(){
	_requestUI();
}

void sendCarPlayRequestUIWithURL( const char * url ){
	_requestUIWithURL(url);
}

void carplayAudioStreamOutputProcessData(int handle, void* inbuffer, int len, int frames, uint64_t timestamp)
{
	AudioStreamOutputProcessData(handle,inbuffer, len, frames, timestamp);
}


void carplayAudioStreamInputProc(int handle, void* buffer, int len, int frame, uint64_t timestamp)
{
	AudioStreamInputProc(handle,buffer, len, frame, timestamp);
}

void carplaySetNightMode(int enter)
{
	_setNightMode(enter);
}

void sendCarPlaySiriButton(int pressed)
{
	_requestSiriAction(pressed);
}

void sendCarPlayMediaButton(int button)
{
	_mediaButtonsUpdate(button);
}

void sendCarPlayTelephonyUpdate(int inButtonIndex)
{
	_telephonyUpdate(inButtonIndex);
}

void sendCarPlayForceKeyFrame()
{
	_forceKeyFrame();
}
